Codify a Full Azure Web Stack with Bicep
Provision an Azure web stack from a single Bicep command using IaC.
Introduction
30 Second Summary
Every cloud project starts the same way. You open a portal, click through menus to set up each piece of your app, and hope you can repeat the whole process next time without missing a step.
In this project, you will write Bicep files that declare an entire Azure web stack as code. You will deploy the full stack with one terminal command, tear it down, and rebuild it from scratch to prove it works every time.
What You'll Build
Type one command in your terminal, wait about a minute, and a live web app appears at a public Azure URL that you can tear down and rebuild from scratch at will.
By the end of this project, you'll have:
- A live web app at a public .azurewebsites.net URL, deployed entirely from Bicep with a single terminal command.
- A modular template with a parameter file that lets you spin up identical stacks with different names by changing one value.
- A proven deploy-and-destroy loop where you tear down the entire stack and rebuild it from scratch, confirming your infrastructure is fully repeatable.
- Secret Mission: Add a Storage Account to the template and prove you can extend the stack declaratively.
Are there any prerequisites?
If you completed the Serverless Portfolio with Azure Functions project, you already have everything you need. Otherwise, Step 1 includes setup tabs to install Azure CLI and create a free Azure account.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Bicep Environment
Before you can deploy infrastructure from code, you need the Bicep toolchain on your machine and a connection to your Azure subscription.
Bicep is Azure's declarative language for Infrastructure as Code, and it works hand-in-hand with the Azure CLI to turn template files into live cloud resources. In this step, you will verify your Azure CLI version, install the Bicep CLI, add the Bicep extension to your editor, and log in to Azure.
In this step, get ready to:
- Verify Azure CLI and install the Bicep CLI.
- Install the Bicep extension in Cursor or VS Code.
- Log in to Azure and confirm your subscription.
Verify Azure CLI and install Bicep
The Bicep CLI is bundled with the Azure CLI, so you need the Azure CLI on your machine first. You should already have it installed from the previous project.
- Check your Azure CLI version by running this command:
az --version
You should see a version number in the output. Version 2.20.0 or later is required for Bicep support.
Seeing an older version or command not found?
If your version is below 2.20.0, update the Azure CLI by following the instructions for your operating system at the Azure CLI install page.
If the command is not found at all, install the Azure CLI from that same page. If you do not have an Azure account yet, create a free one at azure.microsoft.com/free.
Help me troubleshoot my Azure CLI installation.
- Install the Bicep CLI by running this command:
az bicep install
What does this command do?
This downloads and installs the latest Bicep CLI binary on your machine. The Azure CLI manages Bicep as a companion tool, so you install, upgrade, and version-check it through az bicep subcommands.
Already have Bicep installed?
If you have already installed Bicep before, run az bicep upgrade instead to update to the latest version.
Before you run the next command, what output do you expect to see if Bicep installed correctly?
- Verify the installation by running:
az bicep version
You should see output like Bicep CLI version 0.37.4 (or a similar version number). If a version number prints, Bicep is ready.
No version number showing?
Make sure the az bicep install command completed without errors. If you see a permission error, try running your terminal as an administrator (Windows) or prefixing the command with sudo (macOS/Linux).
Help me get Bicep CLI installed and working.
Install the Bicep extension
The Bicep extension gives you syntax highlighting, IntelliSense, and error checking for .bicep files. It catches typos and suggests resource properties as you type, so you will spend less time debugging.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor (or Visual Studio Code) and press Enter to open it.
- Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
- Type Bicep in the search bar.
- Find the extension published by Microsoft (extension ID: ms-azuretools.vscode-bicep) and click Install.
To verify the extension is working, create a new file called test.bicep in your editor. Check the language mode in the bottom-right corner of the editor window. It should display Bicep.
- Delete the test.bicep file after confirming the language mode.
Language mode not showing Bicep?
Make sure the file name ends in .bicep (not .bicep.txt or similar). If you installed the extension but the mode still shows Plain Text, try reloading the editor window by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), then typing Reload Window and pressing Enter.
Help me get the Bicep extension working in my editor.
Log in to Azure
The Azure CLI needs to know which Azure account and subscription to deploy to. Logging in connects your local terminal to your Azure identity.
- Log in to your Azure account by running this command:
az login
What happens when you run this?
A browser window opens automatically. Sign in with your Azure credentials in the browser. After you sign in successfully, the browser tells you to close the tab and the terminal prints your account details.
Before you run the next command, what information do you think Azure will show to confirm you are logged in?
- Verify your subscription is active by running:
az account show --query "{name:name, id:id}" --output table
You should see a table with your subscription name and ID. This confirms your terminal is connected to Azure and ready to deploy resources.
Not seeing your subscription?
If the browser did not open, try running az login --use-device-code instead. This gives you a code to enter manually at microsoft.com/devicelogin.
If you see multiple subscriptions and want to set a specific one as default, use az account set --subscription "Your Subscription Name".
Help me log in to Azure from the CLI.
Your environment is ready. You have the Azure CLI, the Bicep CLI, the Bicep extension, and an active Azure connection. Next up, you will write your first Bicep file and deploy a live web app from it.
Write and Deploy Your First Bicep File
Your Bicep environment is set up and you are logged in to Azure. Now it is time to see IaC in action.
The fastest way to prove that infrastructure as code works is to write a Bicep file that provisions a live web app. Once it is running at a public URL, you have tangible proof that code can create real cloud resources.
In this step, get ready to:
- Create a project folder and write a Bicep file that declares an App Service Plan and Web App.
- Create a resource group and deploy the Bicep file to it.
- Visit your live web app at its public URL.
Write the Bicep file
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir bicep-lab
cd bicep-lab
- Confirm the folder was created by running:
ls ~/Desktop | grep bicep-lab
You should see bicep-lab listed in the output.
- In Cursor (or VS Code), click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the bicep-lab folder.
- Click Open.
You should see an empty bicep-lab folder in the editor's sidebar.
- Create a new file called main.bicep.
- Add the following code to declare the App Service Plan:
// Declare the App Service Plan (hosting environment for the web app)
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: 'bicep-lab-plan'
location: resourceGroup().location
sku: {
name: 'F1' // F1 = Free tier
}
properties: {}
}
What does this code do?
- The resource keyword declares an Azure resource. The type string Microsoft.Web/serverfarms@2023-12-01 identifies this as an App Service Plan using a specific API version.
- name: 'bicep-lab-plan' sets the plan's display name in Azure.
- resourceGroup().location places the plan in the same Azure region as the resource group you will create shortly.
- sku: { name: 'F1' } selects the Free tier, so this costs nothing.
- Save main.bicep.
The Bicep extension validates the file on save. You should see no red underlines in the code.
Seeing red underlines?
- Make sure the file extension is .bicep, not .txt or .json. The Bicep extension only activates for .bicep files.
- Check that the Bicep extension (ms-azuretools.vscode-bicep) is installed and enabled in the Extensions panel.
- Double-check for typos in the resource type string. It must be exactly Microsoft.Web/serverfarms@2023-12-01.
Help me fix red underlines in my Bicep file
- Below the App Service Plan resource in main.bicep, add the Web App resource and an output that prints the live URL:
// Declare the Web App, linked to the App Service Plan above
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: 'bicep-lab-${uniqueString(resourceGroup().id)}' // Globally unique name
location: resourceGroup().location
properties: {
serverFarmId: appServicePlan.id // Links this app to the plan above
httpsOnly: true
}
}
// Output the live URL after deployment
output webAppUrl string = 'https://${webApp.properties.defaultHostName}'
What does this code do?
- serverFarmId: appServicePlan.id links this Web App to the App Service Plan above. Bicep resolves the symbolic reference automatically, so the Web App always deploys after the plan is ready.
- uniqueString(resourceGroup().id) generates a deterministic 13-character hash to make the web app name globally unique across Azure.
- httpsOnly: true enforces HTTPS on the Web App.
- The output line prints the live URL to the terminal after deployment so you can visit it immediately.
- Save main.bicep.
No new red underlines should appear. The file now declares two resources and one output.
Seeing a red underline on appServicePlan?
- Make sure the symbolic name appServicePlan in serverFarmId: appServicePlan.id matches the name on the resource line above exactly. It is case-sensitive.
- Check that both resource blocks have matching opening and closing braces.
Help me fix a reference error in my Bicep file
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.bicep before moving on.
ⓧ I'd like to double check the full code
Your main.bicep should look exactly like this:
// Declare the App Service Plan (hosting environment for the web app)
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: 'bicep-lab-plan'
location: resourceGroup().location
sku: {
name: 'F1' // F1 = Free tier
}
properties: {}
}
// Declare the Web App, linked to the App Service Plan above
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: 'bicep-lab-${uniqueString(resourceGroup().id)}' // Globally unique name
location: resourceGroup().location
properties: {
serverFarmId: appServicePlan.id // Links this app to the plan above
httpsOnly: true
}
}
// Output the live URL after deployment
output webAppUrl string = 'https://${webApp.properties.defaultHostName}'
Deploy to a resource group
Bicep files need a target to deploy into. For now, you will create a resource group manually and deploy the template into it.
- Create a resource group in Azure by running this command:
az group create --name bicep-lab-rg --location eastus
What is a resource group?
A resource group is a logical container in Azure that holds related resources. Every Azure resource must belong to one. Deleting a resource group deletes everything inside it.
The terminal prints a JSON response confirming the resource group was created. You should see "provisioningState": "Succeeded" in the output.
- Deploy your Bicep file to the resource group by running:
az deployment group create \
--resource-group bicep-lab-rg \
--template-file main.bicep
The deployment takes about 30 to 60 seconds. When it finishes, the terminal prints a large JSON output block.
What should I see?
Look for the outputs section near the bottom of the JSON. Inside it, you will find a webAppUrl value containing a URL that ends in .azurewebsites.net. That is the public address of your new web app.
Deployment failed?
- If you see a message about running az login first, your session has expired. Run az login, sign in, and try the deployment command again.
- If the error mentions bicep-lab-rg not being found, make sure you ran the az group create command from the previous step first.
- If you see a conflict error about the web app name, try redeploying. The uniqueString function generates a name tied to your subscription, so conflicts are rare.
Help me troubleshoot my Bicep deployment
Visit the live web app
Before you open the URL, what do you expect to see? This Web App has no application code deployed to it yet.
- Copy the webAppUrl value from the deployment output.
- Paste it into your browser's address bar and press Enter.
You see a page that says your web app is running and waiting for your content. This is the default Azure App Service welcome page.
This is your first resource deployed entirely from code. But take a moment to think about the workflow you just followed.
Why does the manual resource group matter?
You had to run az group create manually before deploying. That command lives in your terminal history, not in your Bicep files. If a teammate needs this stack, they have to know to run that command first.
That manual step is exactly the kind of one-off command IaC is supposed to eliminate. The next step fixes this by bringing the resource group under Bicep control.
Your Bicep file just provisioned a live web app from code. Next, you will eliminate that manual resource group step so one command deploys everything.
Bring the Resource Group Under Bicep Control
Your first Bicep deployment landed a live web app at a public URL. That is a real result you can point your browser at.
There is one gap in the workflow. You had to run az group create manually before deploying. If a teammate clones your repo and runs the deployment command, it fails because the resource group does not exist yet. Subscription-scoped deployment fixes this by letting Bicep create the resource group itself.
In this step, get ready to:
- Delete the manually created resource group.
- Restructure the project into a subscription-scoped main file and a module.
- Deploy the entire stack with a single command.
Delete the manual resource group
Before you restructure the project, tear down the resources you created manually. This gives you a clean slate to prove the new deployment works end to end.
- Delete the resource group by running this command:
az group delete --name bicep-lab-rg --yes --no-wait
What does this command do?
- --yes skips the confirmation prompt so you do not have to type y.
- --no-wait returns control to your terminal immediately while Azure deletes the resources in the background. You can keep working while deletion finishes.
Restructure into main file and module
When you deploy at subscription scope, the resources inside the resource group (App Service Plan, Web App) must live in a separate Bicep module. The main file creates the resource group and calls the module to provision everything inside it.
- Create a modules directory and move your existing Bicep file into it by running these commands:
mkdir modules
mv main.bicep modules/webapp.bicep
- Confirm the file was moved by running:
ls modules/
You should see webapp.bicep listed in the output.
Don't see webapp.bicep?
- Make sure you are in the bicep-lab folder. Run pwd to check your current directory.
- If you see an error saying the file does not exist, double-check that main.bicep was still at the project root before you ran mv.
- Why isn't my mv command working?
The moved file still contains the old resource-group-scoped code. You need to update it so it accepts values from the parent template instead of using hardcoded names.
- Open modules/webapp.bicep in your editor and replace the entire contents with this code:
// Parameters received from the parent template
param location string
param appServicePlanName string
param webAppName string
// App Service Plan on the free tier
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: appServicePlanName
location: location
sku: {
name: 'F1'
}
properties: {}
}
// Web App linked to the plan above
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: webAppName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
}
}
// Return the public URL so the parent template can output it
output webAppUrl string = 'https://${webApp.properties.defaultHostName}'
What changed from the original file?
- Three param declarations replace the hardcoded values. The parent template passes location, appServicePlanName, and webAppName into this module at deploy time.
- resourceGroup().location is gone. A module at resource-group scope can still call resourceGroup(), but using a parameter keeps the module flexible and self-documenting.
- The output line stays the same. It surfaces the web app URL so the parent template can pass it through to the terminal.
- Save modules/webapp.bicep.
- Check the bottom of the editor. The Bicep extension should show no red underlines or error markers in the file.
Seeing red underlines in webapp.bicep?
- Make sure you replaced the entire file contents. Leftover lines from the old version (like resourceGroup().location) cause errors because this module no longer calls resourceGroup() directly.
- Check that all three param declarations are at the very top of the file, before the resource blocks.
- Why am I seeing errors in my Bicep module?
Now you need a new main.bicep at the project root. This file operates at subscription scope so it can create the resource group itself, then call your module to provision the web stack inside it.
- Create a new file called main.bicep in the bicep-lab project root and paste the following code:
// Deploy at subscription level so Bicep can create the resource group
targetScope = 'subscription'
// Configuration for all resources
var location = 'eastus'
var resourceGroupName = 'bicep-lab-rg'
var appServicePlanName = 'bicep-lab-plan'
var webAppName = 'bicep-lab-${uniqueString(subscription().id, resourceGroupName)}'
// Create the resource group as a Bicep-managed resource
resource rg 'Microsoft.Resources/resourceGroups@2025-04-01' = {
name: resourceGroupName
location: location
}
// Deploy the web app module into the resource group
module webapp 'modules/webapp.bicep' = {
name: 'webappDeploy'
scope: rg
params: {
location: location
appServicePlanName: appServicePlanName
webAppName: webAppName
}
}
// Surface the web app URL and resource group name
output webAppUrl string = webapp.outputs.webAppUrl
output resourceGroupName string = rg.name
What does this code do?
- targetScope = 'subscription' tells Bicep this template deploys at subscription level, not inside a resource group. This is what lets you create a resource group from code.
- The resource rg block declares the resource group as a Bicep-managed resource. No more manual az group create.
- The module block calls modules/webapp.bicep and scopes it to rg. Bicep creates the resource group first, then deploys the App Service Plan and Web App inside it.
- uniqueString(subscription().id, resourceGroupName) generates a deterministic 13-character hash so the web app name is globally unique and consistent across deployments.
- Save main.bicep.
- Check that the Bicep extension shows no red underlines. You should see a clean file with no error markers.
Seeing red underlines in main.bicep?
- Check that modules/webapp.bicep exists. The module declaration references this exact path.
- Make sure targetScope = 'subscription' is the very first line of the file. Bicep requires the scope declaration before any other code.
- Why is my subscription-scoped Bicep file showing errors?
✔️ Awesome, I've got everything!
Great. Double check you have saved both main.bicep and modules/webapp.bicep.
ⓧ I'd like to double check the full code
Here is the full main.bicep in the project root:
targetScope = 'subscription'
var location = 'eastus'
var resourceGroupName = 'bicep-lab-rg'
var appServicePlanName = 'bicep-lab-plan'
var webAppName = 'bicep-lab-${uniqueString(subscription().id, resourceGroupName)}'
resource rg 'Microsoft.Resources/resourceGroups@2025-04-01' = {
name: resourceGroupName
location: location
}
module webapp 'modules/webapp.bicep' = {
name: 'webappDeploy'
scope: rg
params: {
location: location
appServicePlanName: appServicePlanName
webAppName: webAppName
}
}
output webAppUrl string = webapp.outputs.webAppUrl
output resourceGroupName string = rg.name
And here is modules/webapp.bicep:
param location string
param appServicePlanName string
param webAppName string
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: appServicePlanName
location: location
sku: {
name: 'F1'
}
properties: {}
}
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: webAppName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
}
}
output webAppUrl string = 'https://${webApp.properties.defaultHostName}'
Deploy the full stack
Both files are ready. One command should now create the resource group and deploy the App Service Plan and Web App inside it.
Before you run the deployment, take a guess. Do you think this single command will create the resource group and web app together, or will something go wrong?
- Deploy the entire stack by running this command:
az deployment sub create \
--location eastus \
--template-file main.bicep
Why is --location required here?
Subscription-scoped deployments need a --location because Azure stores the deployment metadata in a specific region. This is the location of the deployment record itself, not the resources. Your Bicep file controls where the actual resources land.
The deployment takes about 30 to 60 seconds. When it finishes, the terminal prints a JSON output block. Look for the webAppUrl value inside the outputs section.
- Copy the webAppUrl value from the deployment output.
- Open it in your browser.
You should see the default Azure App Service welcome page. This time, the resource group was created entirely by Bicep. No manual az group create needed.
Deployment failed?
- If you see an error about the resource group already existing, the background deletion from earlier may not have finished. Wait a minute and try again.
- If you see a template validation error, run az bicep build --file main.bicep to see detailed error messages. Check that modules/webapp.bicep exists and has the three param declarations.
- Why is my subscription-scoped Bicep deployment failing?
Your entire stack now deploys from a single command. But the resource names are hardcoded in the Bicep file. Next, you will add parameters to make the template reusable across environments.
Add Parameters for Reusability
In the last step, you proved that a single az deployment sub create command can provision an entire Azure web stack from scratch. The resource group, App Service Plan, and Web App all came from Bicep, not from the portal.
There is one problem left. The names bicep-lab-rg, bicep-lab-plan, and the web app name are all hardcoded inside main.bicep. If a teammate wanted to spin up their own copy of this stack, they would have to open the file and edit those values by hand. Parameters fix that by letting you feed different names into the same template without touching the Bicep source.
In this step, get ready to:
- Replace hardcoded values with Bicep parameters.
- Create a .bicepparam parameter file.
- Redeploy using the parameter file.
Parameterize main.bicep
Before deploying a parameterized version of the template, you need to tear down the resources created with the old hardcoded names. This gives you a clean slate so the new deployment does not conflict with the existing resource group.
- Delete the old resource group by running this command:
az group delete --name bicep-lab-rg --yes --no-wait
What does this command do?
- --yes skips the confirmation prompt so the command runs without asking.
- --no-wait starts the deletion in the background so you can keep working while Azure tears down the resources.
Now open main.bicep in your editor. The top of the file currently has four hardcoded var lines that lock every deployment to the same names and region.
- In main.bicep, find these four lines near the top of the file (just below targetScope):
var location = 'eastus'
var resourceGroupName = 'bicep-lab-rg'
var appServicePlanName = 'bicep-lab-plan'
var webAppName = 'bicep-lab-${uniqueString(subscription().id, resourceGroupName)}'
- Select all four var lines and replace them with this code:
// Decorators validate the prefix length at deploy time
@minLength(3)
@maxLength(11)
@description('A prefix used to name all resources')
param namePrefix string
@description('Azure region for all resources')
param location string
// Derive resource names from the prefix parameter
var resourceGroupName = '${namePrefix}-rg'
var appServicePlanName = '${namePrefix}-plan'
var webAppName = '${namePrefix}-${uniqueString(subscription().id, resourceGroupName)}'
What changed here?
The two fixed values (location and the naming prefix) are now param declarations. Bicep parameters are values you supply at deploy time, so the template itself never needs editing.
The @minLength(3) and @maxLength(11) decorators add validation. Azure rejects the deployment up front if someone supplies a prefix that is too short or too long, instead of failing halfway through.
The three var lines remain but now derive their values from namePrefix. Change the prefix and every resource name updates automatically.
- Save main.bicep.
Seeing red squiggles in the editor?
If the Bicep extension highlights errors, make sure you deleted all four original var lines. Leftover lines like var location = 'eastus' conflict with the new param location declaration.
Also check that the targetScope = 'subscription' line at the very top is still intact. The rest of the file (the resource, module, and output blocks) should be unchanged.
Help me fix red squiggles in my main.bicep after adding parameters
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.bicep before moving on.
ⓧ I'd like to double check the full code
Your main.bicep should have four sections in this order. Scroll up and confirm each one is present:
- Line 1: targetScope = 'subscription'.
- Parameters and variables: Two param declarations (namePrefix with @minLength/@maxLength decorators, and location), followed by three var lines that derive names from namePrefix.
- Resource and module: The rg resource and webapp module blocks, unchanged from the previous step.
- Outputs: webAppUrl and resourceGroupName, unchanged.
Create the parameter file
Parameters need values. Instead of typing them into the terminal every time you deploy, you store them in a .bicepparam file that lives alongside your template.
- Create a new file called main.bicepparam in the project root (the bicep-lab folder, next to main.bicep).
- Paste the following into main.bicepparam:
// Point this parameter file at its Bicep template
using './main.bicep'
// Set parameter values for this environment
param namePrefix = 'biceplab'
param location = 'eastus'
What does this file do?
The using './main.bicep' line tells Azure which template this parameter file belongs to. When you deploy with this file, you do not need to specify --template-file separately. Azure reads the using statement and finds the template automatically.
The namePrefix value biceplab means your resource group will be called biceplab-rg, your plan will be biceplab-plan, and the web app gets a name starting with biceplab-.
- Save main.bicepparam.
- Confirm the file exists by running:
ls
You should see main.bicep, main.bicepparam, and the modules folder listed in the output.
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.bicepparam before moving on.
ⓧ I'd like to double check the full code
Your main.bicepparam should look exactly like this:
using './main.bicep'
param namePrefix = 'biceplab'
param location = 'eastus'
Deploy with the parameter file
- Deploy the stack using your new parameter file by running this command:
az deployment sub create \
--location eastus \
--parameters main.bicepparam
Where is the --template-file flag?
When you use a .bicepparam file with a using statement, you do not need the --template-file flag. The parameter file already points to main.bicep, so Azure finds the template automatically.
The deployment takes about 30 to 60 seconds. When it finishes, the terminal prints a JSON output block with an outputs section.
Before you open the URL, think about what the resource group name should be. The old template hardcoded bicep-lab-rg. What should the new name be, given your namePrefix of biceplab?
- Find the resourceGroupName value in the deployment output. It should read biceplab-rg. The prefix you set in the parameter file drove every resource name.
- Copy the webAppUrl value from the output and open it in your browser.
The default Azure App Service welcome page loads. This time the web app, the plan, and the resource group all carry names derived from the biceplab prefix you set in the parameter file.
Deployment failed?
- If you see a naming validation error, make sure your namePrefix in main.bicepparam is between 3 and 11 characters.
- If you see an error about the old resource group bicep-lab-rg still being deleted, wait a minute and re-run the deploy command. The earlier --no-wait flag means Azure may still be cleaning up.
- If you see a template-not-found error, confirm that main.bicepparam contains using './main.bicep' and that both files are in the same folder.
Help me debug a failed Bicep deployment with parameters
Your Bicep template is now fully parameterized. Anyone can spin up a copy of this stack by supplying their own prefix. The last question is the big one: can you destroy everything and rebuild it from scratch to prove the template is truly repeatable?
Prove the Loop: Tear Down and Redeploy
Your Bicep template deploys a full web stack from a parameter file. But how do you know the template actually captures everything?
The real test of Infrastructure as Code is repeatability. In this step, you will destroy your entire Azure environment and rebuild it from scratch using only your Bicep files.
In this step, get ready to:
- Delete the entire resource group and verify the resources are gone.
- Redeploy the full stack from scratch with a single command.
- Confirm the web app is live again at the same URL.
Tear down the stack
If your infrastructure truly lives in code, deleting every cloud resource should feel safe. The Bicep files on your machine are the source of truth, not the running resources in Azure.
- Delete the resource group and everything inside it by running this command:
az group delete --name biceplab-rg --yes
What does this command do?
az group delete removes the resource group and every resource inside it. The App Service Plan, Web App, and the resource group itself all get destroyed.
The --yes flag skips the confirmation prompt so the deletion starts immediately.
The deletion takes about 1-2 minutes. Wait for the command to finish and return you to the terminal prompt.
- Switch to your browser and refresh the web app URL from the previous step.
The page should return an error. The resources no longer exist because you just deleted them.
Page still loading?
- The deletion can take up to 2 minutes. If the page still loads, wait another minute and refresh again.
- Make sure the az group delete command finished in your terminal. If it is still running, wait for it to complete before refreshing.
Still having trouble?
Redeploy from scratch
Your Azure resources are gone. The only thing left is the Bicep files on your machine. This is the moment of truth for your IaC template.
Before you run the next command, think about what you expect to happen. Will the resource group come back? Will the web app get the same URL?
- Redeploy the entire stack by running the same deploy command you used in the previous step:
az deployment sub create \
--location eastus \
--parameters main.bicepparam
Why is this the same command?
This is the exact command you ran in the previous step. Nothing changed in your Bicep files. The template declares the desired state, and Azure provisions it from scratch every time.
Because uniqueString() generates a deterministic hash from your subscription ID and resource group name, the web app gets the same name (and the same URL) every time you deploy.
Wait for the deployment to complete. This takes about 30-60 seconds.
- Copy the webAppUrl value from the deployment output.
- Open the URL in your browser.
The default Azure App Service welcome page loads again. The same web app is back, rebuilt entirely from your Bicep files. You did not click through a single portal screen.
Seeing a 404 or blank page?
- The web app can take an extra 30 seconds to become reachable after deployment finishes. Wait a moment and refresh.
- Make sure you are using the webAppUrl from the new deployment output, not a cached URL. The URL should start with https://.
- If the deployment itself failed, check that main.bicepparam is saved in the project root alongside main.bicep.
Need help debugging?
What is the IaC loop?
You just completed the full IaC loop: define, deploy, tear down, redeploy. Your infrastructure lives in version-controlled files, not in someone's memory of which portal buttons to click.
Anyone with your Bicep files and an Azure subscription can spin up an identical environment with one command. That is the core promise of Infrastructure as Code.
Secret mission
Add a Storage Account
Most real-world web apps need a storage account. Can you extend your Bicep template to provision one alongside the App Service, proving that the IaC pattern scales to any number of resources?
Clean Up Your Resources
Clean Up Your Resources
This project uses the Azure App Service F1 Free tier, which costs nothing. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
If you completed the Secret Mission, your Storage Account is covered by Azure's 12-month free tier for new accounts (5 GB LRS hot block Blob Storage with 20,000 read and 10,000 write operations per month). After the 12-month period, standard storage charges apply.
If your free tier has expired or you do not plan to use the Storage Account, choose Delete below to remove it.
Resources you used:
- Azure Resource Group (biceplab-rg) containing an App Service Plan (F1 Free), Web App, and optionally a Storage Account.
- Local bicep-lab project folder on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your web app live.
- The F1 App Service Plan is free with no time limit. Your web app stays live at its .azurewebsites.net URL.
- If you completed the Secret Mission, the Storage Account is covered by Azure's 12-month free tier for new accounts.
- The Bicep CLI remains installed on your machine alongside Azure CLI. The Bicep VS Code extension stays in your editor.
Pause - I'll come back to this later
No ongoing costs to pause. The F1 tier does not charge, so there is nothing to shut down to save money.
- Your Azure resources continue running at no cost on the F1 Free tier.
- Your local bicep-lab folder stays on your Desktop, ready to redeploy whenever you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your Azure resources and local files permanently.
- Delete the Azure resource group and everything inside it (App Service Plan, Web App, and Storage Account if present) by running:
az group delete --name biceplab-rg --yes
Wait for the deletion to complete. This takes about 1-2 minutes.
- Delete the local project folder. On macOS or Linux, run:
rm -rf ~/Desktop/bicep-lab
- On Windows, run this in PowerShell instead:
Remove-Item -Recurse -Force ~\Desktop\bicep-lab
- Confirm the resource group is gone by running:
az group exists --name biceplab-rg
You should see false in the output.
That's a Wrap!
That's a Wrap!
Nice work! You just codified an entire Azure web stack as Bicep and proved you can spin it up and tear it down with a single command.
You learned how to:
- Write Bicep resource declarations with implicit dependencies via symbolic references, deploying a live App Service Plan and Web App to Azure entirely from code.
- Use subscription-scoped deployments and Bicep modules to provision an entire stack, resource group included, from a single az deployment sub create command.
- Create parameter files (.bicepparam) to make templates reusable across environments and prove the full IaC loop by tearing down and rebuilding the entire stack from scratch.
- Secret Mission: Extended the template with a Storage Account, proving the pattern scales to any number of Azure resources.
Ready to quiz yourself?