Resources, Parameters, Variables, and Outputs
Model Azure infrastructure cleanly — inputs in, resources out, values back to callers.
Simple Explanation (ELI5)
Imagine you fill out an order form to provision a network. Parameters are the fields on the form: environment name, region, address range. Variables are computed shorthand you derive from those inputs, like building the full VNet name from the environment string. The resource block is what actually gets created. Outputs are the receipt — values like the subnet ID returned to the caller so the next step does not need to re-query Azure.
Why Do We Need It?
- One template file deploys consistently to dev, stage, and prod by changing only parameter values.
- Decorators like
@allowedand@minLengthcatch bad inputs before any resource is touched. - Outputs wire deployments together: the VNet module returns the subnet ID, the AKS module consumes it.
- Variables keep complex expressions in one place, reducing duplication and preventing expression drift.
Technical Explanation
Bicep compiles to ARM JSON. Every param, var, and output becomes a corresponding section in the ARM template. Bicep adds a type system with compile-time checking that ARM JSON lacks: string, int, bool, array, object, and secureString are all first-class types.
Parameter Decorators Reference
| Decorator | Purpose | Example use |
|---|---|---|
| @description | Documents the parameter in portal and ARM API | Shown in deployment UX |
| @allowed | Validates input against an explicit allowlist | Environment names, SKU tiers |
| @minLength / @maxLength | String or array length constraint | Storage account: 3-24 chars |
| @secure() | Marks the param secret; ARM never logs its value | Admin passwords, connection strings |
| @minValue / @maxValue | Numeric range constraint | Node count: 1 to 100 |
Real-world Code: Parameterised VNet and Subnet
@description('Target environment: dev, stage, or prod')
@allowed(['dev', 'stage', 'prod'])
param environment string = 'dev'
@description('Azure region for all resources')
param location string = resourceGroup().location
@minLength(3)
@maxLength(50)
param projectName string
// Variables derive reusable values from params
var vnetName = 'vnet-${projectName}-${environment}'
var vnetCidr = environment == 'prod' ? '10.0.0.0/16' : '10.1.0.0/16'
var subnetCidr = environment == 'prod' ? '10.0.1.0/24' : '10.1.1.0/24'
resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: [vnetCidr]
}
subnets: [
{
name: 'snet-workloads'
properties: {
addressPrefix: subnetCidr
}
}
]
}
}
// Outputs surface resource identifiers for downstream consumers
output vnetId string = vnet.id
output vnetName string = vnet.name
output subnetId string = vnet.properties.subnets[0].idUseful Commands
# Compile and validate az bicep build --file main.bicep # Preview changes without deploying az deployment group what-if \ --resource-group rg-platform-dev \ --template-file main.bicep \ --parameters @dev.parameters.json # Deploy az deployment group create \ --resource-group rg-platform-dev \ --template-file main.bicep \ --parameters @dev.parameters.json \ --name deploy-networking # Capture an output for the next pipeline step az deployment group show \ -g rg-platform-dev -n deploy-networking \ --query 'properties.outputs.subnetId.value' -o tsv
Hands-on Steps
- Save the template as
main.bicepand runaz bicep buildto verify no compile errors. - Create
dev.parameters.jsonwithprojectName=platformandenvironment=dev. - Run what-if to review the planned changes before committing.
- Deploy to a dev resource group and confirm the VNet appears in the portal.
- Capture the subnetId output and pass it to an AKS module in the next tutorial.
Debugging Scenarios
Scenario 1: Required Parameter Missing from .parameters.json
Symptom: Azure CLI reports InvalidParameter: Required parameter 'projectName' has no value
- Open the
.parameters.jsonfile and ensure all required params (those without defaults) have entries. - Run
az bicep build— the compiler shows which params lack defaults. - Add the missing key to the parameters file; format is
"projectName": { "value": "platform" }.
Scenario 2: @allowed Validation Rejected at Pre-flight
Symptom: ARM returns InvalidTemplateDeployment: Allowed values are dev, stage, prod
- The caller passed a value not in the
@allowedlist (for exampleproductioninstead ofprod). - This is caught before any resource is created — fix the parameters file and redeploy.
Scenario 3: Output References a Property Path That Does Not Exist
Symptom: Bicep compile error referencing an undefined property on the resource object
- Verify the API version declared on the resource supports that property path.
- Check the Azure REST API reference for the exact property structure under that API version.
- Update the API version or correct the property path to match.
Interview Questions
Beginner
A param is an external input provided by the caller at deployment time. A var is an internally computed value derived inside the template and not settable by the caller.
When most deployments share a reasonable baseline. Defaults reduce what callers must specify while still allowing targeted overrides, such as a larger VM SKU in prod.
It instructs ARM to treat the value as a secret. The value is never logged in deployment history or shown in the portal, and it cannot appear in outputs or variables that flow into unsecured properties.
They return values like subnet IDs and key vault URIs to caller templates so downstream modules can consume them without re-querying Azure or hardcoding resource names.
Azure rejects the deployment at pre-flight validation before any resource is created, returning a clear error with the allowed values listed.
Intermediate
The networking module declares an output for the subnet ID. The root template captures it as networkingModule.outputs.subnetId and passes it as a parameter to the AKS module declaration.
ARM explicitly blocks this. If you try to assign a secure param to an output, the deployment fails with a clear error. This prevents secrets from appearing in deployment history or being readable via APIs.
Use conditional variable expressions: var vmSku = environment == 'prod' ? 'Standard_D4s_v3' : 'Standard_B2s'. A single environment parameter drives all sizing decisions consistently.
When a group of settings always travel together, such as network configuration (addressPrefix, subnetName, nsgName). An object param keeps them cohesive and simplifies the module interface.
It prevents deployment with an invalid name at pre-flight, before the resource is attempted. Azure storage accounts must be 3-24 characters; catching it early avoids a runtime failure mid-deployment.
Scenario-based
A hardcoded value means the same template cannot target multiple environments. Move the environment name to a param with @allowed and a default of 'dev'. The same template now deploys correctly to any environment.
The address space should be either a parameter or a variable driven by the environment parameter. Each environment gets a distinct CIDR block, eliminating the collision at design time.
Declare a subnetId output in the template. After deployment, extract it with az deployment group show and the --query flag. Set it as a pipeline variable for the next step that provisions workloads.
Real-world Usage
Production Bicep projects keep a single main.bicep template alongside per-environment parameter files: dev.parameters.json, stage.parameters.json, prod.parameters.json. The CI/CD pipeline selects the right parameter file based on the branch or environment context. This pattern keeps infrastructure definitions consistent while allowing environment-specific overrides for sizing, SKU tiers, and CIDR blocks.
Summary
Parameters make templates reusable across environments. Variables keep complex expressions DRY and readable. Outputs wire deployments together in modular architectures. Mastering decorators, defaults, and output chaining separates practical Bicep fluency from superficial familiarity.