Hard-Coded Templates Cannot Be Reused? Use Parameters, Variables, and Outputs

Published on:

The first CloudTrips template deployed a DEV storage account, but several decisions were still fixed in the code. Copying the complete template for TEST would create two files that can drift apart.

Refactor the template so the caller supplies environment-specific decisions, Bicep calculates consistent values, and the deployment returns the information needed by later commands.

In this trip, reuse one main.bicep file to deploy a CloudTrips TEST storage account.

Refactor the Template

Replace the current main.bicep content with:

targetScope = 'resourceGroup'

@description('CloudTrips deployment environment.')
@allowed([
  'dev'
  'test'
  'prod'
])
param environment string

@description('Azure region for the storage account.')
param location string = resourceGroup().location

@description('Storage redundancy option.')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_ZRS'
])
param storageSku string = 'Standard_LRS'

@description('Team or person responsible for the resource.')
param owner string

@description('Application name used in resource tags.')
param applicationName string = 'CloudTrips'

var normalizedEnvironment = toLower(environment)
var storageAccountName = 'stct${normalizedEnvironment}${uniqueString(resourceGroup().id)}'
var commonTags = {
  Application: applicationName
  Environment: toUpper(normalizedEnvironment)
  ManagedBy: 'Bicep'
  Owner: owner
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
  name: storageAccountName
  location: location
  tags: commonTags
  sku: {
    name: storageSku
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
  }
}

output storageAccountName string = storageAccount.name
output storageAccountId string = storageAccount.id
output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob

What does SKU mean?

SKU stands for Stock Keeping Unit. In Azure, it identifies a service tier or configuration that affects features, resilience, and cost. For storage, Standard is the performance tier and the suffix specifies how Azure replicates the data:

  • LRSLocally Redundant Storage; copies remain within one datacenter, making it suitable for this TEST exercise and other replaceable non-production data
  • ZRSZone-Redundant Storage; copies are distributed across availability zones in one region so the service can withstand a zone failure
  • GRSGeo-Redundant Storage; copies are also sent to a secondary Azure region for regional disaster recovery, using asynchronous replication and requiring failover

ZRS and GRS are not a simple better-or-worse sequence: ZRS protects regional availability across zones, while GRS provides a second-region copy.

The three constructs have different jobs:

  • param receives a deployment decision, such as test or Standard_LRS
  • var derives consistent names and tags inside the template
  • output returns deployed values for people, scripts, or pipelines to use

The @allowed lists reject unsupported environment and SKU values during validation.

Check the Bicep File

Run the Bicep linter:

az bicep lint \
  --file main.bicep

No output means the linter found no diagnostics. Fix any reported error before continuing.

Prepare CloudTrips TEST

Select the CloudTrips TEST subscription:

az account set \
  --subscription "<CloudTrips TEST subscription name or ID>"

Create the TEST resource group:

az group create \
  --name rg-cloudtrips-bicep-test-weu \
  --location westeurope \
  --tags Application=CloudTrips Environment=TEST ManagedBy=Bicep

The template itself is unchanged. TEST-specific choices are supplied as parameter values.

Preview the TEST Deployment

Run:

az deployment group what-if \
  --resource-group rg-cloudtrips-bicep-test-weu \
  --template-file main.bicep \
  --parameters \
    environment=test \
    storageSku=Standard_LRS \
    owner=DmytroKlymenko@cloudtrips.onmicrosoft.com

Confirm that the preview contains one storage account with the Create change type. Its tags must include Environment: TEST and the supplied owner.

What-if preview for the reused Bicep template showing the CloudTrips TEST storage account as a new resource

Deploy with TEST Values

Deploy the same template:

az deployment group create \
  --name deploy-cloudtrips-storage-test \
  --resource-group rg-cloudtrips-bicep-test-weu \
  --template-file main.bicep \
  --parameters \
    environment=test \
    storageSku=Standard_LRS \
    owner=DmytroKlymenko@cloudtrips.onmicrosoft.com \
  --query "properties.{state:provisioningState,outputs:outputs}" \
  --output yaml

The --query expression only shortens the command output. It shows the deployment state and the values declared with output; it does not change the deployment.

Confirm:

state: Succeeded
outputs.storageAccountName.value: stcttest...
outputs.storageAccountId.value: /subscriptions/.../storageAccounts/stcttest...
outputs.blobEndpoint.value: https://stcttest....blob.core.windows.net/

Verify the Reused Template

Copy the returned storage-account name and inspect the resource:

storage_account_name="<storageAccountName output>"

az storage account show \
  --resource-group rg-cloudtrips-bicep-test-weu \
  --name "$storage_account_name" \
  --query "{name:name,sku:sku.name,environment:tags.Environment,owner:tags.Owner,managedBy:tags.ManagedBy}" \
  --output table

Verify that the result contains:

SKU: Standard_LRS
Environment: TEST
Owner: DmytroKlymenko@cloudtrips.onmicrosoft.com
ManagedBy: Bicep

CloudTrips TEST storage account created from the reusable template with the expected SKU and tags

The team now has one template for multiple environments. Parameters expose the decisions that may change, variables apply the naming and tagging rules, and outputs make the deployment result available without duplicating the template.