Copyright (c) 2026 MindMesh Academy. All rights reserved. This content is proprietary and may not be reproduced or distributed without permission.

4.1.2.4. Automate Deployments by Using Bicep Files

4.1.2.4. Automate Deployments by Using Bicep Files

💡 First Principle: Bicep changes how you write a deployment, not how Azure runs one. The Bicep tooling compiles your file to ARM JSON before anything reaches Resource Manager, so every rule you already know from ARM templates (idempotence, incremental mode, what-if) still applies. What you gain is a file that is short enough to read and safe enough to edit.

Scenario: You inherit a main.bicep file that deploys a storage account. You must make the SKU configurable per environment and restrict it to approved values, deploy the file to test and production with different settings, preview the production change first, and bring a resource group that someone built by hand in the portal under the same code.

What It Is: Bicep is a domain-specific language for deploying Azure resources declaratively. The deployment commands compile a .bicep file automatically, so Resource Manager only ever receives ARM JSON, and there is no separate Bicep service to enable.

Reading a Bicep File
@description('Prefix for the storage account name.')
@minLength(3)
@maxLength(11)
param storagePrefix string

@allowed([
  'Standard_LRS'
  'Standard_ZRS'
])
param storageSku string = 'Standard_LRS'

param location string = resourceGroup().location

var storageName = '${storagePrefix}${uniqueString(resourceGroup().id)}'

resource stg 'Microsoft.Storage/storageAccounts@2025-06-01' = {
  name: storageName
  location: location
  sku: {
    name: storageSku
  }
  kind: 'StorageV2'
}

output blobEndpoint string = stg.properties.primaryEndpoints.blob
ElementWhat it tells you
paramA value supplied at deployment time. = 'Standard_LRS' is the default used when no value is passed.
Decorators (@allowed, @minLength, @maxLength, @minValue, @maxValue, @description, @secure())Constraints and metadata on the line below. A value outside @allowed fails validation before any resource is touched. @secure() (string or object only) keeps the value out of deployment history and logs.
varA value computed inside the file. It can't be supplied at deployment time.
resource stg '<type>@<apiVersion>'stg is a symbolic name used only inside the file (for example stg.id). The name property is the resource name you see in Azure.
outputA value returned after deployment, such as an endpoint another step needs.
moduleA call to another Bicep file by path, which deploys that file's resources as part of this deployment.
No targetScope lineThe file deploys to a resource group, which is the default scope.

Referencing another resource's symbolic name (stg.id, stg.properties...) creates an implicit dependency, so Resource Manager deploys the referenced resource first without any dependsOn.

Modifying an Existing Bicep File

The edits the exam expects you to recognize are small and mechanical:

  1. Parameterize a hard-coded value, and give the new parameter a default so existing deployments that don't pass it keep working.
  2. Constrain the input with @allowed([...]), @minLength/@maxLength, or @minValue/@maxValue.
  3. Protect secrets with @secure() rather than a plain string parameter.
  4. Add a resource that references an existing one by symbolic name, which creates the dependency for you.
  5. Keep every non-default property of a resource you edit. Redeploying reapplies the whole declaration, so a deleted property is reset to its default (see 4.1.2.3).

Parameter names can contain letters, digits, and underscores but no periods. When the decompiler meets an ARM parameter name with periods, it replaces them with underscores.

Deploying a Bicep File

A Bicep parameters file (.bicepparam) holds the values for one environment. Its using statement names the Bicep file it belongs to:

using 'main.bicep'

param storagePrefix = 'contoso'
param storageSku = 'Standard_ZRS'
# The parameters file's 'using' statement names main.bicep, so no --template-file is needed
az deployment group create \
  --resource-group rg-app-prod \
  --parameters main.prod.bicepparam

# Override one value inline; an inline value wins over the parameters file
az deployment group create \
  --resource-group rg-app-test \
  --parameters main.test.bicepparam \
  --parameters storageSku=Standard_LRS

# Preview creates, modifications, and deletions without changing anything
az deployment group what-if \
  --resource-group rg-app-prod \
  --template-file main.bicep \
  --parameters storageSku=Standard_ZRS
New-AzResourceGroupDeployment `
  -ResourceGroupName rg-app-prod `
  -TemplateFile main.bicep `
  -TemplateParameterFile main.prod.bicepparam

A parameters file may set only parameters that the Bicep file declares; an extra name causes an error. Standard JSON parameters files still work with Bicep. Parameters files store values as plain text, so retrieve secrets from Key Vault with the getSecret function instead of writing them into the file.

Exporting and Converting
Starting pointToolResult
Resources in a resource group, or one resourceExport template in the portalA Bicep file or ARM JSON template generated as a snapshot of current state, including later manual changes. Values are mostly hard-coded and it carries properties you wouldn't normally set, so expect to clean it up. Limited to resource groups of 200 resources or fewer.
A past deploymentResource group > Deployments > select the deployment > TemplateThe exact ARM JSON template that was deployed (JSON only), with its parameters. It doesn't include changes made after that deployment.
An ARM JSON templateaz bicep decompile --file main.jsonmain.bicep in the same folder, as a starting point to review
A Bicep fileaz bicep build --file main.bicepmain.json, useful for seeing what Resource Manager receives (deployment runs this for you)
A JSON parameters fileaz bicep decompile-paramsA .bicepparam file

Microsoft's own guidance is that export isn't guaranteed to succeed and isn't a reliable way to produce production-ready code; some password parameters can be missing from an exported template.

Visual: The Bicep Deployment Loop

⚠️ Common Pitfall: Treating an exported or decompiled file as finished code. Exported templates hard-code values and include properties you wouldn't normally set, and decompiled files may need renaming and restructuring. Parameterize, review, and run what-if before you deploy either one.

Key Trade-Offs:
  • Resource group export vs. deployment history: A resource group export reflects what exists now, including drift, but needs cleanup. The deployment-history template is clean and parameterized but reflects only what was originally deployed.
  • Bicep vs. ARM JSON: Bicep is shorter, supports modules by file path, and infers dependencies. ARM JSON is what Resource Manager actually receives, so reading it (via az bicep build) is still useful when troubleshooting.

Reflection Question: If Bicep compiles to ARM JSON before deployment, which ARM template behaviors (idempotence, incremental property reapplication, complete-mode deletion) should you expect to carry over unchanged, and how would you use what-if to catch an unintended change before it reaches production?

See how it connects
Alvin Varughese
Written byAlvin Varughese
Founder•20 professional certifications