Convert ARM to Safer Bicep
Convert an ARM template to Bicep and safely enforce HTTPS on an Azure web app.
Introduction
30 Second Summary
Something can work exactly as expected while still hiding a risky setting in the fine print. A safer update starts by finding that gap before changing what people already use.
In this project, you will deploy a Node.js Hello World app to Azure App Service from an ARM JSON template. You will convert that infrastructure to Bicep so you can use a what-if preview before enabling HTTPS-only access.
What You'll Build
Opening the finished app through an HTTP address lands your browser on the secure HTTPS version of the still-working Node.js Hello World page.
By the end of this project, you'll have:
- A live Node.js app that loads from a public Azure address after you deploy its infrastructure from ARM JSON.
- A readable Bicep template where symbolic resource relationships make the App Service configuration easier to review.
- A safe deployment loop that previews the HTTPS-only modification before applying it to the live app.
- Secret Mission: Move the F1 SKU plus the southcentralus region into a reusable .bicepparam file.
Are there any prerequisites?
You need an active Azure subscription with permission to create resource groups plus App Service resources. The project uses Azure Cloud Shell in your browser, so your Mac needs no local Azure infrastructure tools.
Before We Start
This is your moment to lock in the migration goal before any Azure resources exist. You will define why converting a working ARM template to Bicep makes the security update easier to review and preview safely.
Open Your Zero-Install Azure Workstation
Your ARM-to-Bicep migration needs Azure tooling before you can deploy anything. Installing a local toolchain would consume the project without teaching infrastructure as code.
Azure Cloud Shell gives you an authenticated terminal in your browser. Its ephemeral Bash mode provides the required tools without a local installation.
In this step, get ready to:
- Start an ephemeral Bash session without persistent storage.
- Verify the managed Azure toolchain against your active subscription.
- Open a blank main.json file in the Cloud Shell editor.
Start an ephemeral Cloud Shell session
Cloud Shell can run without attached storage. The temporary option suits this lab because your files only need to last through the active session.
- Go to the Azure portal in your browser.
- Sign in with the account linked to your active Azure subscription.
- Select Cloud Shell from the portal toolbar.
- Select Bash when Cloud Shell asks you to choose a shell.
- Choose the option to continue without attaching a storage account.
You’ll see an authenticated Bash prompt in the Cloud Shell panel. Your zero-install Azure workstation is now running in the browser.
Why Use an Ephemeral Session?
Cloud Shell itself is free. Persistent Azure Files storage carries regular storage costs.
An ephemeral session keeps the workspace temporary. Files saved here disappear when the Cloud Shell window closes.
Cloud Shell Not Starting?
- Confirm that your signed-in account has an active Azure subscription.
- Restart Cloud Shell from the portal toolbar if the Bash prompt does not load.
- Choose the temporary storage option again if Cloud Shell returns to the storage screen.
Share the visible session state without sharing credentials when you ask for help troubleshooting Cloud Shell.
Verify the managed Azure tools
Azure Cloud Shell authenticates the Azure CLI against a default subscription. The managed environment also includes the Bicep CLI used later for conversion.
Before you run the checks, which Azure subscription do you expect Cloud Shell to use?
- Confirm the active subscription plus both managed tool versions by running these commands:
az account show --output table
az version
az bicep version
What Do These Checks Prove?
- The account check identifies the default subscription used by later deployment commands.
- The Azure CLI version check confirms that the managed command-line environment is available.
- The Bicep CLI version check confirms that the conversion tools are ready.
You’ll see your active subscription in a table. You’ll also see installed Azure CLI plus Bicep CLI version information.
That clears the setup hurdle. Your browser terminal is authenticated with both deployment tools available.
Missing Subscription or Version Details?
- Confirm that the Cloud Shell selector shows Bash.
- Restart the ephemeral session if a managed tool does not respond.
- Check your subscription access in the Azure portal if the subscription table is empty.
Use the output you can safely share when you ask for help with the Azure tool checks.
Open the blank ARM template file
The ARM baseline begins in main.json. The Cloud Shell editor uses the same temporary workspace as your terminal.
Before you run this command, what should an untouched JSON file contain?
- Open main.json directly in the Cloud Shell editor by running this command:
code main.json
What Does This Command Do?
- The code command opens a file directly in the Cloud Shell editor.
- The main.json filename establishes the workspace for your ARM template.
- Press Cmd+S on macOS or Ctrl+S on Windows to save main.json.
- Check the editor tab for the filename main.json.
You’ll see a blank editor pane with main.json open. Your ARM workspace is ready for the baseline template.
Editor Not Opening?
- Confirm that you entered the command at the active Bash prompt.
- Check the editor tab for the exact filename main.json.
- Run the editor command again if the editor pane closed.
Describe the editor state you can see when you ask for help opening main.json.
✔️ Awesome, I've got everything!
Your blank main.json file is saved and open in the Cloud Shell editor.
ⓧ I'd like to double check the full code
The complete main.json file contains zero characters at this checkpoint.
- Compare your editor pane with the required blank state.
- Remove every character if anything appears in the editor.
- Save main.json after confirming that it is empty.
Your browser-based Azure workstation is authenticated with the deployment tools ready. Next, you’ll use this blank file to deploy the working ARM baseline.
Deploy the ARM Baseline
Your zero-install Azure Cloud Shell workstation is ready. Now the migration needs a working source deployment you can trust.
An ARM template becomes your known-good baseline for Azure App Service. Its original settings stay intact so the next review uses evidence from a live resource.
In this step, get ready to:
- Save the complete ARM JSON template in main.json.
- Deploy the complete App Service baseline.
- Open the live Node.js Hello World app.
Save the ARM template
The template describes every resource Azure creates. Parameters hold reusable values while variables generate stable resource names.
- Switch back to the blank main.json file in the Cloud Shell editor from earlier.
- Replace its blank contents with the complete ARM JSON reference in the second tab below.
✔️ Awesome, I've got everything!
Your main.json file now defines the complete baseline deployment.
ⓧ I'd like to double check the full code
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"sku": {
"type": "string",
"defaultValue": "F1",
"allowedValues": [
"F1"
]
},
"linuxFxVersion": {
"type": "string",
"defaultValue": "node|24-lts"
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"repositoryUrl": {
"type": "string",
"defaultValue": "https://github.com/Azure-Samples/nodejs-docs-hello-world"
},
"branch": {
"type": "string",
"defaultValue": "main"
}
},
"variables": {
"appServicePlanName": "[toLower(format('AppServicePlan-{0}', uniqueString(resourceGroup().id)))]",
"webSiteName": "[toLower(format('wapp-{0}', uniqueString(resourceGroup().id)))]"
},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2023-12-01",
"name": "[variables('appServicePlanName')]",
"location": "[parameters('location')]",
"kind": "linux",
"sku": {
"name": "[parameters('sku')]"
},
"properties": {
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-12-01",
"name": "[variables('webSiteName')]",
"location": "[parameters('location')]",
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
"httpsOnly": false,
"siteConfig": {
"linuxFxVersion": "[parameters('linuxFxVersion')]"
}
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
},
{
"type": "Microsoft.Web/sites/sourcecontrols",
"apiVersion": "2023-12-01",
"name": "[format('{0}/web', variables('webSiteName'))]",
"properties": {
"repoUrl": "[parameters('repositoryUrl')]",
"branch": "[parameters('branch')]",
"isManualIntegration": true
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('webSiteName'))]"
]
}
],
"outputs": {
"webAppName": {
"type": "string",
"value": "[variables('webSiteName')]"
}
}
}
How is the template organized?
- The parameters block centralizes values that the resources reuse.
- The variables block generates names from the resource group identifier.
- The resources array declares the App Service plan, web app, and source-control configuration.
- The web app resource keeps httpsOnly at false for this baseline.
- The outputs block exposes the generated web app name.
- Save main.json by pressing Cmd+S on macOS or Ctrl+S on Windows.
The editor now shows the saved template with its parameters, variables, three resources, and output.
Is the template still blank?
- Confirm that you pasted the template into the existing main.json editor tab.
- Check that the first character is { and the final character is }.
- Help me fix my ARM template file.
Create the baseline deployment
Creating cloud resources can feel like a billing moment. This template pins the App Service plan to the F1 free SKU.
- Switch back to the Bash prompt in your Azure Cloud Shell session.
- Create the resource group and deploy the ARM template by running these commands:
RG=rg-arm-to-bicep-lab
az group create --name "$RG" --location southcentralus
az deployment group create --resource-group "$RG" --name arm-first --template-file main.json
What do these commands create?
- The RG shell variable holds the resource group name for later commands.
- Azure CLI creates rg-arm-to-bicep-lab in southcentralus.
- The deployment named arm-first sends main.json to the resource group.
- Azure creates the Linux App Service plan, web app, and external source configuration from that template.
- Wait for Cloud Shell to return to the Bash prompt.
When both commands complete, the prompt returns below the deployment output. Your resource group now contains the complete ARM baseline.
Did the deployment fail?
- Confirm that your active subscription permits resource group and App Service deployments.
- Check that main.json is saved in the current Cloud Shell directory.
- Help me troubleshoot the ARM deployment.
Open the live web app
The template generates the web app name from the resource group identifier. Reading the deployed resource gives you the exact hostname Azure assigned.
- Read the deployed app name and print its HTTPS URL by running these commands:
APP_NAME=$(az webapp list --resource-group "$RG" --query "[0].name" --output tsv)
HOST=$(az webapp show --resource-group "$RG" --name "$APP_NAME" --query defaultHostName --output tsv)
echo "https://$HOST"
What do these commands capture?
- The APP_NAME variable stores the generated web app name.
- The HOST variable stores the app's default hostname.
- The final command prints a complete HTTPS URL that you can open in your browser.
Cloud Shell prints one URL beginning with https://.
The first load may take a few minutes while Azure App Service starts the app. Before you open the URL, what do you expect this source deployment to serve?
- Copy the printed HTTPS URL.
- Open a new tab in your browser.
- Paste the URL into the browser address bar.
- Press Enter.
You'll see the Azure Samples Node.js Hello World app load from your new App Service hostname.
Can't load the sample app?
- Refresh the browser after a short wait if App Service is still starting the application.
- Confirm that you copied the URL printed by echo "https://$HOST".
- Help me troubleshoot the deployed web app.
That's your first visible win. The ARM template now serves a working Node.js app from Azure.
Your ARM baseline is live. Next, you'll inspect its deployed settings before converting the working JSON template to Bicep.
Find the Gap and Convert to Bicep
Your ARM template has already put a live Node.js app on Azure App Service. The working page proves the deployment succeeded.
A working page cannot tell you whether the app rejects plain HTTP. That security property must be checked against the deployed resource.
The verbose JSON also makes critical settings harder to review. You will expose the gap before converting the working template into Bicep.
In this step, get ready to:
- Inspect the deployed web app's HTTPS-only property.
- Convert the working ARM JSON template into a Bicep file.
- Review the generated resources before validating the Bicep syntax.
Inspect the live HTTPS setting
The httpsOnly property controls whether the web app accepts only HTTPS requests. Azure CLI can read the deployed value directly from App Service.
Before you run the query, do you think the deployed app requires HTTPS?
- Inspect the live web app property by running this command in the Cloud Shell terminal from earlier:
az webapp show --resource-group "$RG" --name "$APP_NAME" --query "{name:name,httpsOnly:httpsOnly}" --output table
What does this query check?
- The query reads the existing web app identified by APP_NAME inside the resource group stored in RG.
- The selected fields show the generated app name plus its current httpsOnly value.
- The table format keeps the security property easy to spot.
You'll see false under httpsOnly. This is the intended shortfall in the working baseline.
You've found the live configuration gap. The app still accepts plain HTTP requests.
Web app query not returning a row?
Check that RG still contains rg-arm-to-bicep-lab. Check that APP_NAME still contains the generated web app name.
Help me troubleshoot the Azure CLI web app query.
Convert the ARM template
Decompilation rewrites the deployed structure from ARM JSON syntax into Bicep syntax. Your Azure Cloud Shell session already includes the required decompiler.
- Create main.bicep beside main.json by running this command:
az bicep decompile --file main.json
What does decompilation do?
The decompiler reads main.json and creates main.bicep in the same directory. The original ARM template remains unchanged.
Decompilation provides a best-effort starting point. Human review confirms that the generated structure preserves the intended resources.
Decompilation not completing?
Confirm that the saved source file is named main.json. Confirm that your Cloud Shell terminal is in the directory containing that file.
Help me troubleshoot Bicep decompilation.
- Open the generated main.bicep file in the Cloud Shell editor by running this command:
code main.bicep
What does this command do?
The command opens main.bicep directly in the Cloud Shell editor. This gives you a readable view of the generated parameters and resources.
You'll see a populated Bicep file in the editor. The generated file sits beside the unchanged main.json source template.
Generated file looks blank?
Check that the editor tab is named main.bicep. Switch back to the Cloud Shell terminal to confirm that the decompile command completed before opening the file.
Help me inspect the generated Bicep file.
Review and lint the Bicep file
Decompilation needs human review because some ARM expressions require manual revision. You will confirm that this conversion preserved the original parameters and resource relationships.
- Locate the sku, linuxFxVersion, location, repositoryUrl, and branch parameters in main.bicep.
- Locate the appServicePlanName and webSiteName variables.
- Identify the appServicePlan, appService, and srcControls resources.
- Trace the App Service plan relationship through appServicePlan.id.
- Trace the source-control relationship through parent: appService.
- Confirm that the generated app resource still contains httpsOnly: false.
- Compare your generated file with the reference by choosing the tab that matches your confidence:
✔️ Awesome, I've got everything!
Your generated file contains the five parameters, two variables, three resources, and one output. Keep main.bicep open for the lint check.
ⓧ I'd like to double check the full code
@allowed([
'F1'
])
param sku string = 'F1'
param linuxFxVersion string = 'node|24-lts'
param location string = resourceGroup().location
param repositoryUrl string = 'https://github.com/Azure-Samples/nodejs-docs-hello-world'
param branch string = 'main'
var appServicePlanName = toLower('AppServicePlan-${uniqueString(resourceGroup().id)}')
var webSiteName = toLower('wapp-${uniqueString(resourceGroup().id)}')
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: appServicePlanName
location: location
kind: 'linux'
sku: {
name: sku
}
properties: {
reserved: true
}
}
resource appService 'Microsoft.Web/sites@2023-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: false
siteConfig: {
linuxFxVersion: linuxFxVersion
}
}
}
resource srcControls 'Microsoft.Web/sites/sourcecontrols@2023-12-01' = {
parent: appService
name: 'web'
properties: {
repoUrl: repositoryUrl
branch: branch
isManualIntegration: true
}
}
output webAppName string = webSiteName
What should match?
- The parameters preserve the free F1 SKU plus the Node.js runtime and repository settings.
- The three resources preserve the App Service plan plus the web app and source-control configuration.
- The symbolic references express how the resources depend on each other.
- The httpsOnly: false setting preserves the insecure baseline you observed.
The linter checks the generated file for syntax errors and Bicep rule violations. This gives you a valid baseline before any security change.
Before you lint the file, do you expect the converted template to pass unchanged?
- Validate the generated Bicep file by running this command:
az bicep lint --file main.bicep
What does the lint check prove?
The linter parses main.bicep without deploying anything. A clean result proves the converted file is valid Bicep before you edit it.
The terminal returns to the prompt with no error diagnostics. Your converted Bicep file is valid.
Seeing lint diagnostics?
Compare main.bicep with the full reference above. Check the parameter declarations plus the braces around each resource.
Help me understand my Bicep lint diagnostics.
Your working ARM template now has a valid Bicep counterpart. Next, you'll make the resource relationships easier to review before previewing the HTTPS-only change.
Refactor and Preview the Safer Configuration
Your converted Bicep template is valid. Now you can turn that functional starting point into a reviewed definition you trust to update the live app.
Its Azure App Service relationships already use symbolic references. A what-if preview lets you inspect the HTTPS-only change before Azure applies it.
In this step, get ready to:
- Review the free SKU guardrail and symbolic resource relationships.
- Enable HTTPS-only and lint the updated Bicep template.
- Preview the live infrastructure change without applying it.
Review the symbolic resource relationships
The converted file uses symbolic resource names to connect the App Service plan to the web app. The source-control child also points to the web app through its parent.
- Return to main.bicep in the Cloud Shell editor.
- Find the @allowed block that contains F1.
- Find serverFarmId: appServicePlan.id inside appService.
- Find parent: appService inside srcControls.
Why is this refactor small?
The decompiler already produced the intended symbolic structure for this source template. The F1 guardrail also remains in place.
The appServicePlan.id reference connects the web app to its plan. The parent: appService declaration connects the source-control child to the web app.
These symbolic links let Bicep infer the resource dependencies. Your focused edit can preserve this readable structure.
Enable HTTPS-only and lint the template
The live app currently accepts HTTP because httpsOnly is set to false. Setting the property to true defines the safer state in the template.
- In main.bicep, find the line where httpsOnly is set to false.
- Replace that line with the version below:
httpsOnly: true
What does this change do?
Setting httpsOnly to true configures the web app to accept secure requests. An HTTP request gets redirected to HTTPS after deployment.
- Save main.bicep.
- Check the edited file by running:
az bicep lint --file main.bicep
What does lint check?
The lint command checks main.bicep for syntax errors and Bicep linter rule violations. A clean run returns no error diagnostics.
You should see no error diagnostics from the lint command.
Good progress. Your safer template now passes Bicep's static checks.
Seeing Lint Diagnostics?
A diagnostic at the edited property usually points to a misspelling or an indentation problem.
- Check that the property reads httpsOnly: true.
- Align the property with serverFarmId inside the same properties block.
Help me diagnose my Bicep lint output.
✔️ Awesome, I've got everything!
Your edited main.bicep file is saved and passes linting.
ⓧ I'd like to double check the full code
@allowed([
'F1'
])
param sku string = 'F1'
param linuxFxVersion string = 'node|24-lts'
param location string = resourceGroup().location
param repositoryUrl string = 'https://github.com/Azure-Samples/nodejs-docs-hello-world'
param branch string = 'main'
var appServicePlanName = toLower('AppServicePlan-${uniqueString(resourceGroup().id)}')
var webSiteName = toLower('wapp-${uniqueString(resourceGroup().id)}')
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: appServicePlanName
location: location
kind: 'linux'
sku: {
name: sku
}
properties: {
reserved: true
}
}
resource appService 'Microsoft.Web/sites@2023-12-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: linuxFxVersion
}
}
}
resource srcControls 'Microsoft.Web/sites/sourcecontrols@2023-12-01' = {
parent: appService
name: 'web'
properties: {
repoUrl: repositoryUrl
branch: branch
isManualIntegration: true
}
}
output webAppName string = webSiteName
What should match?
Compare the parameters and variables first. The F1 guardrail should remain unchanged.
Check that appService uses appServicePlan.id. Check that srcControls uses parent: appService.
The security property should read httpsOnly: true.
Preview the live change
A what-if preview compares the desired Bicep state with the current resources. It gives you a review point before a deployment can change the live app.
Before you run the preview, do you expect Azure to report a new resource or a modification to the existing web app?
- Preview the resource-group deployment by running:
az deployment group what-if --resource-group "$RG" --template-file main.bicep
What does this command do?
The command compares main.bicep with the resources in rg-arm-to-bicep-lab. It reports the proposed changes without applying them.
A Modify result means the resource already exists. One or more of its properties would change during deployment.
You should see a Modify result for the App Service app with httpsOnly changing from false to true. The live property remains false because this command only previews the change.
Seeing Extra Changes?
Azure resource providers can include default properties in the preview. Those extra entries can create noise around the change you intended to review.
- Focus on the Modify result for httpsOnly.
- Ignore unrelated provider-default noise for this project.
Help me interpret my Azure what-if result.
That preview is your safety checkpoint. Next, you will apply the reviewed Bicep template and prove the live app redirects HTTP requests to HTTPS.
Redeploy from Bicep and Prove the Fix
Your what-if preview showed the exact security change without applying it. The live Azure App Service app still uses the original setting.
Now you will apply the reviewed Bicep file from Azure Cloud Shell.
The final checks prove HTTPS-only is live. They also prove the Node.js app remains available.
In this step, get ready to:
- Apply the reviewed main.bicep file as the bicep-secure deployment.
- Confirm the live app reports HTTPS-only as enabled.
- Prove the app redirects HTTP requests to its working HTTPS page.
Apply the reviewed Bicep deployment
A preview predicts how Azure plans to change your resources. A resource-group deployment applies the desired state from main.bicep to the existing app.
- Create the bicep-secure deployment from main.bicep by running this command:
az deployment group create --resource-group "$RG" --name bicep-secure --template-file main.bicep
What does this deployment do?
- The command sends the desired state in main.bicep to the resource group stored in $RG.
- The name bicep-secure gives this reviewed update its own deployment record.
- Azure updates the existing symbolic resources to match the Bicep definition.
- Keep the Cloud Shell session active until the deployment finishes.
- Confirm the command returns you to the Bash prompt.
You'll see deployment details for bicep-secure before the Bash prompt returns.
Good progress. The previewed configuration is now a live deployment.
Deployment did not complete?
- Review the deployment output for the resource that failed.
- Confirm that main.bicep remains saved in the current Cloud Shell session.
Help me diagnose this Bicep deployment failure.
Verify the live App Service state
The deployment result confirms that Azure accepted the update request. A resource query now checks the live App Service state directly.
What value do you expect the live service to report now? Make your prediction before you run the check.
- Read the deployed properties and refresh HOST by running these commands:
az webapp show --resource-group "$RG" --name "$APP_NAME" --query "{name:name,state:state,httpsOnly:httpsOnly,host:defaultHostName}" --output table
HOST=$(az webapp show --resource-group "$RG" --name "$APP_NAME" --query defaultHostName --output tsv)
What do these checks prove?
- The first command reads the current App Service state from Azure.
- Its query includes the live httpsOnly property.
- The query also includes the hostname needed for the browser test.
- The second command refreshes HOST with that deployed hostname.
The table reports true for httpsOnly. It also shows that the app is running.
That is your first live proof. The reviewed Bicep deployment changed the security property successfully.
Still seeing HTTPS-only disabled?
- Review the preceding deployment output for a failure.
- Confirm that main.bicep still contains httpsOnly: true.
- Run the property check again after the deployment finishes.
Help me find why the deployed App Service still reports HTTPS-only as disabled.
Test the HTTP-to-HTTPS redirect
HTTPS-only redirects an HTTP request to the secure HTTPS address. This browser test proves the policy works without breaking the sample app.
Where do you expect the browser to finish? Make your prediction before you open the address.
- Copy the hostname shown in the Host column of the Cloud Shell table.
- Switch to a new browser tab.
- Type http:// into the address bar.
- Paste the copied hostname after the prefix.
- Press Enter.
Your browser finishes at an address beginning with https://. The Azure Samples Node.js Hello World app still loads.
That closes the loop. Your Bicep deployment now enforces HTTPS without interrupting the live app.
Redirect or app not working?
- Enter a fresh address beginning with http:// to test the redirect again.
- Return to Azure Cloud Shell.
- Confirm that the property table reports the app as running.
Help me troubleshoot the final App Service redirect test.
Secret mission
Separate Environment Values
Move the free SKU and deployment region into a reusable Bicep parameters file. Deploy through that file to prove the secure configuration still reaches your live web app.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep your Azure resources online, pause the web app for later, or delete the project entirely. Both the current F1 Azure App Service plan and the ephemeral Azure Cloud Shell session are free.
Cost warning
Your current App Service plan stays within the free F1 tier.
A paid App Service SKU introduces charges.
Persistent Azure Files storage uses regular storage pricing.
Resources you used:
- Azure resource group named rg-arm-to-bicep-lab.
- Linux App Service plan using the F1 tier.
- Node.js Hello World web app referenced by APP_NAME.
- External Git source-control configuration attached to the web app.
- Three ARM deployment records inside the resource group.
- Ephemeral Cloud Shell session used for the project.
- ARM template main.json in the ephemeral session.
- Bicep template main.bicep in the ephemeral session.
- Bicep parameters file main.dev.bicepparam in the ephemeral session.
Keep everything running
Keep this option if you want the sample app available for future demonstrations. The deployed web app remains online in your Azure subscription.
- Your rg-arm-to-bicep-lab resource group remains in your subscription.
- Your Node.js Hello World app remains available through its public URL.
- Your deployment records remain available for reviewing the ARM-to-Bicep migration.
- Your App Service plan remains on the free F1 tier.
- Your Cloud Shell files remain available only for the current ephemeral session.
Pause - I'll come back to this later
Pause the web app if you want to take it offline without removing its infrastructure. The resource group remains available for a later restart.
- Stop the web app from the Cloud Shell session by running this command:
az webapp stop --name "$APP_NAME" --resource-group "$RG"
What does this command do?
- APP_NAME identifies the web app you deployed earlier.
- RG points to rg-arm-to-bicep-lab.
- The command stops the app without deleting its App Service plan or deployment history.
- Return to the sample app page from earlier.
- Refresh the browser page.
You should no longer reach the running Node.js Hello World app. You have paused the public app without deleting its infrastructure.
Could not stop the app?
- A restarted ephemeral session no longer has RG or APP_NAME set.
- Recreate those values with the commands from Step 2 before retrying the stop command.
If the command still fails, help me troubleshoot the Azure web app stop command.
Delete - I don't want to use this again
This option permanently removes every Azure resource inside the project resource group. Choose it only when you have finished using the live app.
- Delete the resource group from the Cloud Shell session by running this command:
az group delete --name "$RG" --yes --no-wait
What does this command remove?
- RG targets rg-arm-to-bicep-lab.
- Deleting the resource group also removes its App Service plan, web app, source-control configuration, and deployment records.
- The --yes option skips the confirmation prompt.
- The --no-wait option returns control to Cloud Shell while Azure continues the deletion.
- Return to the Azure portal.
- Search your subscription for rg-arm-to-bicep-lab.
- Refresh the resource-group list until rg-arm-to-bicep-lab no longer appears.
- Close the Cloud Shell browser tab to end the ephemeral session.
Once the resource group disappears, the live app and its supporting resources are gone.
Closing Cloud Shell removes main.json, main.bicep, and main.dev.bicepparam from the ephemeral session. Your Azure resources and temporary workspace are now cleaned up.
Still see the resource group?
- Remember that --no-wait allows deletion to continue after the command returns.
- Refresh the resource-group list after Azure finishes processing the deletion.
- Confirm that the portal shows the same subscription you used in Cloud Shell.
If the group remains, help me check the Azure resource group deletion.
Nice Work!
Nice Work!
You did it! You transformed a working ARM template into a safer Bicep deployment on Azure App Service. Your Node.js Hello World app remains live with secure browser access enforced.
What you learned:
- Deployed a live Node.js web app from ARM JSON. You confirmed the sample app was reachable through its public hostname.
- Found the intentional HTTPS-only gap. You decompiled the working template before refactoring its dependencies with symbolic resource references.
- Used a what-if preview to review the security change before deployment. You redeployed from Bicep before proving the live app redirected HTTP requests to HTTPS.
- Secret Mission: Created a reusable native Bicep parameters file that keeps the free SKU and deployment region outside the main template.
Ready to quiz yourself?