The Bicep Constitution: Separating Architectural Law from Environmental Fact

Mid/Senior Engineer Asked at: Microsoft, any company on Azure

Q: You've parameterized a Bicep template. What's the best practice for providing values for those parameters, especially when managing multiple environments like dev and prod?

Why this matters: This question tests your ability to design for scale and safety. A junior engineer knows how to use parameters. A senior engineer architects a system that separates configuration from logic, a fundamental principle for reducing risk and cognitive overhead in any complex system.

Interview frequency: High for any role that involves production IaC.

❌ The Death Trap

The candidate describes an ad-hoc approach, either mixing default values in the main template or relying solely on command-line overrides.

"You can just provide the values on the command line when you deploy. Or, for dev, you can just put a default value in the `param` block in the main bicep file and then override it for prod."

This is a brittle, error-prone strategy. It leads to configuration drift, makes code reviews difficult, and violates the core principle of separating logic from environment-specific configuration.

🔄 The Reframe

What they're really asking: "How do you codify your environment-specific configurations so they are as version-controlled, auditable, and reliable as the infrastructure logic itself? What is the architectural pattern for separating the 'what' from the 'how'?"

This reframes the question from a syntax discussion to a strategic one about configuration management. It's about building a system that is transparent, scalable, and safe by design.

🧠 The Mental Model

The "Constitution and Bylaws" model. Your infrastructure is a nation, and it needs a clear separation of powers.

1. The Bicep Template is the Constitution (`main.bicep`): This document lays out the fundamental, unchanging laws and structures of your nation (the application architecture). It defines the branches of government (resources) and the powers they have. It should be abstract and timeless.
2. The Parameter Declarations are the Bill of Rights: The `param` blocks inside the Constitution define the rights and variables that must be respected, but they don't assign the specific values. They say "there shall be a President," but not "the President is named Joe."
3. The Parameter File is the Bylaws for a Specific State (`prod.bicepparam`): This is a separate, concrete document that applies the Constitution to a specific context. It contains the *facts*: "In the state of Production, the President is named 'prod-server-01'," "The official currency is 'Premium_LRS'."

📖 The War Story

Situation: "I was on a platform team where our main Bicep template had become a monstrous, 1000-line file with dozens of parameters, all with default values set for our `dev` environment."

Challenge: "Deploying to production was terrifying. It involved a long, complex command-line invocation with over 20 `-set` arguments to override the dev defaults. A single typo in this command could (and did) cause a catastrophic misconfiguration, like deploying a tiny dev-sized database into production. Code reviews were meaningless because the real configuration wasn't in the file; it was in the deployment script."

Stakes: "We had created a system where the riskiest and most important deployment—to production—was also the most manual and error-prone. We were one fat-fingered command away from a major outage."

✅ The Answer

My Thinking Process:

"The core principle being violated was the separation of concerns. Our constitutional law (`.bicep` logic) was polluted with the facts of one specific state (`dev` default values). My mission was to architect a clean separation, making our configuration explicit, version-controlled, and auditable."

What I Did: From a Single Monolith to a Clean Separation

I introduced Bicep parameter files (`.bicepparam`) as the architectural solution. This created a clear boundary between our logic and our configuration.

1. The Constitution (`main.bicep`)

// This file now ONLY declares the contract. // It contains no environment-specific values. @description('The globally unique name') param storageAccountName string @description('The Azure region') param location string resource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = { name: storageAccountName location: location sku: { name: 'Standard_LRS' } kind: 'StorageV2' }

2. The Bylaws (`prod.bicepparam`)

// This file provides the FACTS for production. // It is a simple, reviewable list of values. using './main.bicep' param storageAccountName = 'prod-storage-xyz123' param location = 'eastus'

The `using './main.bicep'` statement is the critical link. It explicitly declares that this parameter file is a set of inputs *for* the `main.bicep` template. This allows the Bicep extension in VS Code to provide strong validation and IntelliSense, ensuring our bylaws are always in sync with our constitution.

The Deployment: A Clear Mandate

Our deployment command became simpler and safer. Instead of a long list of overrides, we now pass a single, explicit configuration file:

New-AzResourceGroupDeployment -ResourceGroupName "Prod-RG" -TemplateFile "main.bicep" -TemplateParameterFile "prod.bicepparam"

The Outcome:

"This change was transformative. Code reviews for production deployments became trivial; the approver only needed to review the `prod.bicepparam` file, a simple list of key-value pairs. The risk of 'fat-fingering' a command-line argument was eliminated. We had created a clear, auditable, and version-controlled source of truth for each environment's configuration."

🎯 The Memorable Hook

This connects the technical best practice to a deep, first-principles concept about the separation of theory and reality, demonstrating a higher level of architectural thinking.

💭 Inevitable Follow-ups

Q: "What's a limitation of parameter files, and how do you work around it? For example, using a function like `resourceGroup().location`."

Be ready: "You're right, parameter files can't use runtime functions like `resourceGroup()` because they are evaluated before the deployment context exists. This is a deliberate design choice to keep them as simple 'fact sheets.' The elegant solution is in the main `.bicep` file itself. You define the parameter with a default value that uses the function: `param location string = resourceGroup().location`. This creates a 'smart default.' The template will automatically use the resource group's location unless explicitly overridden by a value in the parameter file. It's the best of both worlds."

Q: "How do you handle secrets in parameter files? You wouldn't check a production database password into Git."

Be ready: "You absolutely do not. The parameter file should not contain the secret itself, but a *reference* to the secret. The best practice is to store the secret in Azure Key Vault. Then, in the `.bicepparam` file, you pass a reference to that secret. The Bicep template then uses a `existing` resource block to read the Key Vault and a `getSecret()` function to resolve the value at deployment time. This keeps the secret out of your source code entirely."

Written by Benito J D