Environment configuration variables fail to substitute correctly in ARM template deployments

We’re deploying ARM templates to multiple environments (dev, staging, prod) using environment-specific variable groups. The deployment succeeds, but variables aren’t being substituted in the template parameters file.

Our pipeline uses a single ARM template with different parameters.json files per environment. Variables like $(sqlServerName) and $(storageAccountName) remain as literal strings instead of being replaced with actual values from the variable group.

{
  "parameters": {
    "sqlServerName": {
      "value": "$(sqlServerName)"
    },
    "storageAccountName": {
      "value": "$(storageAccountName)"
    }
  }
}

The variable groups are correctly scoped to each environment (Dev-Variables, Staging-Variables, Prod-Variables) and linked in the pipeline. We’re using sequential deployment across environments. Has anyone dealt with variable precedence rules or environment-specific substitution issues in ARM deployments?

The issue is that parameters.json files aren’t automatically processed for variable substitution in ARM deployment tasks. You need to explicitly enable this feature. Here’s the complete solution:

Variable Group Scoping: First, ensure your variable groups are properly scoped:

  1. Go to Pipelines → Library → Select each variable group
  2. Under “Pipeline permissions”, grant access to your pipeline
  3. In your pipeline YAML, reference groups at the stage level, not globally:
stages:
- stage: Dev
  variables:
  - group: Dev-Variables

This ensures each stage uses only its environment-specific variables.

Environment-Specific Substitution: The ARM deployment task requires explicit token replacement. Use the overrideParameters approach instead of relying on parameters.json substitution:

- task: AzureResourceManagerTemplateDeployment@3
  inputs:
    deploymentScope: 'Resource Group'
    azureResourceManagerConnection: 'Azure-Connection'
    resourceGroupName: '$(resourceGroupName)'
    location: 'East US'
    templateLocation: 'Linked artifact'
    csmFile: 'infrastructure/template.json'
    overrideParameters: |
      -sqlServerName "$(sqlServerName)"
      -storageAccountName "$(storageAccountName)"

This bypasses the parameters.json file entirely and injects variables directly into the deployment.

Sequential vs Parallel Deployment: For sequential deployment, add explicit dependencies:

- stage: Staging
  dependsOn: Dev
  variables:
  - group: Staging-Variables

This prevents variable context bleeding between stages.

Variable Precedence Rules: Azure DevOps resolves variables in this order (highest to lowest):

  1. Task-level variables
  2. Stage-level variables
  3. Pipeline-level variables
  4. Variable groups

Ensure you’re not defining conflicting variables at multiple levels. Use stage-scoped variable groups to avoid precedence issues.

The overrideParameters method is more reliable than file-based substitution because it processes variables at runtime within the task context, ensuring proper environment-specific values are used regardless of how the parameters file is structured.

ARM template deployments don’t automatically substitute variables in parameters files. You need to enable file transformation in your AzureResourceManagerTemplateDeployment task. Add the parameter csmParametersFileLink and set deploymentMode to use variable substitution. The task has to explicitly know to process the file for token replacement.

Variable precedence rules can cause unexpected behavior. Pipeline variables override variable group values, and stage-level variables override pipeline-level ones. Check if you have any conflicting variable definitions.

Check if your variable group scoping is correct. Even if the groups are linked to the pipeline, they might not be scoped to the specific stage or environment. Go to Pipelines → Environments → select your environment → click Approvals and checks → verify variable groups are listed there. Also make sure there are no duplicate variable definitions across groups that could cause precedence conflicts.

Also consider whether you’re using sequential vs parallel deployment. If stages run in parallel, variable group scoping can get confused about which environment’s variables to use. Sequential deployment with proper stage dependencies ensures each environment gets its own variable context.