Infrastructure as code makes cloud environments repeatable, but repeatability does not automatically make deployments safe.
A small Bicep change can have consequences far beyond the edited line. Renaming a resource may cause Azure to create a replacement rather than update the existing resource. Changing a subnet definition can affect connected services. Removing a resource declaration can delete production infrastructure when using complete-mode deployments or deployment stacks. A parameter value intended for development can accidentally point a production deployment at the wrong SKU, region, or network.
The Bicep file may compile successfully. Validation may pass. The pull request may look harmless. None of those outcomes tells you exactly what Azure believes it is about to change.
That is the gap Azure Resource Manager’s What-If operation is designed to close.
What-If evaluates a Bicep deployment against the current Azure environment and produces a predicted change set before anything is applied. It is not a perfect transaction plan, and it should not be treated as one. However, used correctly, it gives Cloud Engineers, DevOps teams, Platform Engineers, and SREs a practical way to detect destructive changes, challenge unexpected drift, and make infrastructure reviews substantially more meaningful.
Generate an Azure Bicep Plan with What-If
Bicep is a declarative language for authoring Azure Resource Manager deployments. You describe the desired state of resources, and Azure Resource Manager determines the operations required to move the target scope toward that state.
That abstraction is valuable because engineers do not need to manually orchestrate every API call. It also creates an important operational question: what operations will Azure Resource Manager infer from the desired state?
In HashiCorp Terraform, teams commonly answer that question with terraform plan. In Azure Bicep, the closest equivalent is the Azure Resource Manager What-If operation.
For a resource-group deployment, run:
az deployment group what-if \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparam
The same pattern is available at other deployment scopes:
az deployment sub what-ifaz deployment mg what-ifaz deployment tenant what-if
The command must match the target scope of the Bicep deployment:
groupfor a resource groupsubfor a subscriptionmgfor a management grouptenantfor a tenant
A typical production workflow separates preview from deployment:
az bicep build --file main.bicepaz deployment group validate \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparamaz deployment group what-if \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparamaz deployment group create \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparam
Compilation catches Bicep syntax and type problems. Validation checks whether Azure Resource Manager can process the deployment. What-If predicts the resulting infrastructure changes. Deployment applies them.
For interactive use, Azure CLI can combine preview and confirmation:
az deployment group create \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparam \ --confirm-with-what-if
This is convenient for engineer-driven deployments, but automated pipelines usually benefit from keeping the What-If and deployment stages separate. Separation creates a review boundary where policy checks, approvals, and change analysis can occur before production credentials are allowed to apply anything.
Pull Requests Show Intent, Not Impact
Infrastructure pull requests describe intent, not impact.
Suppose an engineer changes this App Service configuration:
resource webApp 'Microsoft.Web/sites@2023-12-01' = { name: appName location: location properties: { httpsOnly: true serverFarmId: appServicePlan.id siteConfig: { minTlsVersion: '1.2' } }}
The textual diff may show only two values changing. That is useful, but incomplete. Reviewers still need answers to several questions:
- Does the deployed resource currently match the repository?
- Will Azure update the existing resource or replace it?
- Will the provider normalize or default additional properties?
- Are parameter files changing resource names, regions, or SKUs?
- Does the deployment include modules whose effects are not visible in the edited file?
- Will a removed declaration result in an actual deletion?
The repository diff cannot answer those questions because it compares one version of code with another. What-If compares the proposed desired state with Azure’s view of the deployed state.
That distinction matters in production environments where drift is normal. Emergency portal changes, policy remediation, provider defaults, out-of-band automation, incident response, and older pipeline versions can all cause the live environment to diverge from source control.
A pull request might propose no apparent infrastructure change while the resulting deployment attempts to reverse operational drift. Conversely, a large code refactor might produce no effective cloud changes at all.
Another common misconception is that successful validation implies a safe deployment. Validation can confirm that a template is structurally acceptable and that many references and values are valid. It does not tell you whether the deployment will delete a public IP address, downgrade a SKU, replace a resource because its name changed, or remove a production tag used by governance automation.
The engineering challenge is therefore not simply “Does this Bicep compile?” It is “What does Azure believe this declaration means for the current environment?”
Insight and Analysis
The most useful way to think about What-If is as an impact review, not merely a preview command.
The default output is a resource-level and property-level diff. It resembles the following:
Resource and property changes are indicated with these symbols: + Create - Delete ~ Modify = Nochange * IgnoreThe deployment will update the following scope:Scope: /subscriptions/.../resourceGroups/rg-platform-prod + Microsoft.Storage/storageAccounts/stplatformlogs [2023-05-01] location: "eastus" name: "stplatformlogs" sku.name: "Standard_GRS" ~ Microsoft.Web/sites/api-platform-prod [2023-12-01] ~ properties.httpsOnly: false => true ~ properties.siteConfig.minTlsVersion: "1.0" => "1.2" ~ tags.release: "2026.05" => "2026.06" - Microsoft.Network/publicIPAddresses/pip-legacy [2023-09-01]Resource changes: 1 to create, 1 to delete, 1 to modify.
The symbols provide the first level of interpretation:
| Symbol | Change type | Operational meaning |
|---|---|---|
+ | Create | Azure predicts a new resource or property |
- | Delete | Azure predicts removal of a resource or property |
~ | Modify | An existing value is expected to change |
= | NoChange | The declared and current values are equivalent |
* | Ignore | The resource is present but not part of the effective deployment change |
! | Deploy | Azure expects deployment activity but cannot produce a precise property diff |
The symbols are simple. The engineering judgment behind them is not.
Treat deletions as a separate risk class
A deletion should never be reviewed as merely another line in the output.
The first question is whether the deletion is intentional. The second is whether the resource is stateful, externally referenced, or difficult to recreate. The third is whether deletion is a direct result of the proposed change or a consequence of deployment behavior.
Deletion deserves special scrutiny when working with:
- Complete deployment mode
- Deployment stacks with deny or delete behavior
- Resource renames
- Resources moved between modules
- Conditional resource declarations
- Loops whose keys or indexes changed
- Resources whose names are derived from parameters
- Child resources omitted from a revised declaration
A rename is especially important because many Azure resources are identified by name. Changing the Bicep symbolic name alone does not rename the Azure resource, but changing the deployed name property can result in one resource being created and another becoming eligible for deletion.
Seeing one + Create and one - Delete for similar resources is often the signature of replacement rather than an in-place update.
Read modifications in terms of service behavior
A property change is not automatically low risk.
Changing a tag is usually operationally different from changing an address space, private endpoint configuration, identity type, database SKU, availability zone, or encryption setting. What-If shows structural differences; engineers must map those differences to service behavior.
Consider this output:
~ Microsoft.Sql/servers/databases/orders ~ sku.name: "GP_S_Gen5_2" => "BC_Gen5_8"
The output is one modified property. Operationally, it could affect cost, capacity, availability characteristics, connection behavior, and the duration of the control-plane operation.
A mature review process classifies changes by blast radius rather than counting changed lines. One useful model is:
- Identity changes: names, resource IDs, scopes, tenant associations
- Network changes: subnets, routes, firewall rules, DNS, private endpoints
- State changes: storage, databases, disks, retention, backup settings
- Availability changes: zones, replication, SKUs, scaling limits
- Security changes: identities, role assignments, TLS, encryption, public access
- Governance changes: tags, locks, diagnostics, policy-related configuration
What-If provides evidence. The reviewer provides context.
Expect noise from provider behavior
What-If predictions can include false positives.
Azure resource providers frequently add default values, reorder arrays, normalize casing, expose read-only properties, or return an expanded representation that differs from the submitted Bicep declaration. Azure Resource Manager may then report changes even though the next deployment will not produce a meaningful operational difference.
For example:
~ properties.someSetting: null => "Default"
This may indicate a real configuration change, or it may indicate that the provider returns "Default" when the template omits the property.
Array-heavy resources can be particularly noisy. Firewall rules, access policies, routing rules, application settings, and identity lists may appear reordered even when their effective meaning has not changed.
The wrong response is to ignore all noisy output. The better response is to establish a baseline:
- Run What-If against the currently deployed commit.
- Record recurring provider-generated differences.
- Run it again against the proposed commit.
- Focus review on new or materially changed predictions.
Persistent noise is still technical debt. It weakens the signal engineers rely on during high-risk changes. Where practical, explicitly declare stable provider defaults, normalize array construction, and use consistent API versions to reduce unnecessary differences.
Use output filtering carefully
For large deployments, unchanged resources can overwhelm the review. Azure CLI can exclude selected change types:
az deployment group what-if \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparam \ --exclude-change-types NoChange Ignore
This is useful in CI because reviewers usually care most about creates, deletes, and modifications.
However, aggressive filtering can hide context. An unchanged dependency may explain why another resource is being modified. During incident-sensitive or high-blast-radius changes, preserving the full output as a pipeline artifact while presenting a filtered summary gives teams both readability and traceability.
For automation, use machine-readable output:
az deployment group what-if \ --resource-group rg-platform-prod \ --template-file main.bicep \ --parameters @main.prod.bicepparam \ --no-pretty-print
The JSON result can be evaluated by pipeline logic. Teams can fail or pause deployments when the predicted changes include:
- Any resource deletion
- Changes to protected resource types
- Public network access being enabled
- Production SKUs being reduced
- Role assignments being removed
- Diagnostic settings being deleted
- Changes outside an approved resource group or subscription
The goal is not to encode every architectural decision into a shell script. It is to automate obvious safety boundaries so human reviewers can spend their attention on ambiguous changes.
Make What-If part of the deployment contract
The strongest implementation pattern is not “engineers may run What-If.” It is “every production deployment produces a reviewable What-If result.”
A practical pipeline separates responsibilities:
Build -> Lint -> Validate -> What-If -> Policy checks -> Human or automated approval -> Deploy -> Verify
The What-If stage should use the same:
- Bicep source
- Parameter files
- Template-spec version
- Deployment scope
- Deployment mode
- Identity and permissions
- Environment-specific configuration
Using different inputs for preview and deployment defeats the purpose.
Permissions matter as well. What-If must inspect existing resources and evaluate the proposed deployment. A pipeline identity with incomplete read access may produce partial results or fail to evaluate nested resources. Preview accuracy is therefore partly an identity-design concern.
Teams should also minimize the delay between preview and apply. What-If does not create an immutable deployment plan that Azure later executes verbatim. Azure evaluates the deployment again when create runs. Another pipeline, operator, policy assignment, or autoscaling process can change the environment between those steps.
That limitation does not make What-If ineffective. It means the correct mental model is pre-deployment evidence, not state locking.
For high-risk environments, combine What-If with deployment serialization, environment locks in the CI/CD platform, short approval windows, policy enforcement, and post-deployment verification.
Review surprises, not just expected changes
The highest-value line in a What-If result is often the one nobody expected.
An unexpected modification may reveal drift. An unexpected create may expose a naming error or an incorrect target scope. An unexpected deletion may reveal a conditional expression that evaluated differently with production parameters. A Deploy result without detailed property changes may identify a provider limitation that deserves a manual check.
Teams should require change authors to explain three things:
- Which predicted changes are expected?
- Which predicted changes are provider noise?
- Which predicted changes were unexpected, and how were they resolved?
This turns What-If from pipeline output into an engineering control. It also improves shared understanding of Azure resource-provider behavior—knowledge that is otherwise learned during outages.
Conclusion
Azure Bicep What-If gives infrastructure teams something source-code review alone cannot provide: Azure’s prediction of how the proposed desired state differs from the live environment.
Run it with the deployment command that matches your scope, review creates, modifications, and deletions according to operational impact, and expect some provider-generated noise. Treat deletions and replacement patterns as high-risk. Interpret property changes through the behavior of the affected Azure service, not through the size of the textual diff.
Most importantly, make What-If a standard stage in production delivery rather than an optional troubleshooting command.
It is not a guaranteed execution plan, a state lock, or a substitute for validation, policy, testing, and post-deployment verification. It is a decision-quality tool. It gives engineers a chance to identify when declared intent, live state, and Azure’s interpretation do not agree.
That gap is where many infrastructure incidents begin. What-If lets you inspect it before deployment turns disagreement into downtime.