Deploy a Bicep file at the right scope — validate, preview, deploy
Deploy Bicep so it works the first time and redeploys safely — pick the scope (resource group, subscription, management group, tenant) and targetScope, use a .bicepparam file, compile, validate and what-if (validation levels, confirm-with-what-if), deploy in incremental mode and know what incremental really does, use modules across scopes, read secrets from Key Vault at deploy time, and avoid complete mode.
markdownguidev1RKED-DQ1Cstage: buildtrust: officialservice: bicepinterface: iacinterface: cliaz deployment group createaz deployment sub createtargetScope subscriptionbicepparamusing main.bicepaz deployment group what-if--confirm-with-what-if--validation-level Provider ProviderNoRbac Templateincremental modecomplete mode deprecatedmodule scope resourceGroup subscriptionexisting resource BicepgetSecret Key Vault BicepenabledForTemplateDeploymentMicrosoft.KeyVault/vaults/deploy/actiondeployment location subscriptionuniqueStringBicep CLI install
Deploy a Bicep file at the right scope
A Bicep file deploys to one scope, declared by targetScope (default resourceGroup), with the matching command. Anything at another scope goes through a module with scope:. Then: compile, preview, deploy, all in incremental mode.
1. Scope and command
targetScope | Command | Typical contents |
|---|---|---|
'resourceGroup' (default) | az deployment group create --resource-group <rg> | Most resources |
'subscription' | az deployment sub create --location <loc> | Resource groups, subscription policy and role assignments |
'managementGroup' | az deployment mg create --management-group-id <mg> --location <loc> | Policy and RBAC across subscriptions |
'tenant' | az deployment tenant create --location <loc> | Management groups |
For non-group scopes, --location is where the deployment record is stored, not where resources go. Reusing a deployment name with another location fails (InvalidDeploymentLocation). Deploying a resource that belongs to another scope directly fails at compile time, for example BCP135: Scope "resourceGroup" is not valid for this resource type. Permitted scopes: "subscription".
2. A subscription-scope file that creates its group, then deploys into it
targetScope = 'subscription'
param location string = 'eastus2'
param name string = 'orders'
resource rg 'Microsoft.Resources/resourceGroups@2025-04-01' = {
name: 'rg-${name}'
location: location
}
module storage 'storage.bicep' = {
scope: rg
params: {
location: location
prefix: name
}
}
output storageId string = storage.outputs.id
// storage.bicep (targetScope defaults to resourceGroup)
param location string
param prefix string
resource sa 'Microsoft.Storage/storageAccounts@2026-06-01' = {
name: take('st${prefix}${uniqueString(resourceGroup().id)}', 24)
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
}
}
output id string = sa.id
uniqueString(resourceGroup().id) gives a stable, globally unlikely name. take(…, 24) keeps it inside the storage name limit.
Pick API versions Bicep has types for. The newest REST API version isn't always in the Bicep CLI's type library: storage 2026-09-01 is in the REST specs, but Bicep 0.48.1 has no types for it and warns BCP081 … does not have types available (no property checking at all). Use the newest version that compiles without BCP081, here 2026-06-01.
3. Parameters in a .bicepparam file
using './main.bicep'
param location = 'eastus2'
param name = 'orders'
Pass it with --parameters main.bicepparam. Parameter files are plain text, so never put secrets in them. Read a secret at deploy time instead: param adminPassword = getSecret('<sub-id>', '<rg>', '<vault>', '<secret>') (or kv.getSecret('<secret>') in a .bicep file for a @secure() module parameter). That needs the vault's enabledForTemplateDeployment: true and, for the deployer, Microsoft.KeyVault/vaults/deploy/action (included in Owner and Contributor).
4. Compile, validate, preview, deploy
bicep build main.bicep # BCP errors and warnings, with line and column
az deployment sub validate --location eastus2 --template-file main.bicep --parameters main.bicepparam
az deployment sub what-if --location eastus2 --template-file main.bicep --parameters main.bicepparam
az deployment sub create --location eastus2 --template-file main.bicep --parameters main.bicepparam \
--name orders-$(date +%Y%m%d%H%M) --confirm-with-what-if
--validation-level(CLI 2.76+):Provider(default, includes permission checks),ProviderNoRbac(checks only read permission: useful for a reviewer identity),Template(syntax only).- What-if limits: it doesn't expand
templateLink(linked templates and template specs), stops expanding after 800 resource groups, and since CLI 2.75 reports what it couldn't analyze ("short-circuiting"). Treat anIgnoreor unanalyzed entry as unknown, not as "no change". --confirm-with-what-if/-cshows the preview and asks before deploying: right for humans. In CI, runwhat-ifas its own step and review it.
5. What incremental mode really does
- Incremental (the default) leaves resources not in the file alone. That's why it's safe.
- For resources that are in the file, every property is reapplied, not merged. A property you leave out goes back to its default.
- Define subnets in the VNet's
subnetsarray, not as separate child resources, or a redeploy of the VNet removes them. Web app settings live in theMicrosoft.Web/sites/configchild. - Complete mode deletes everything in the group that isn't in the file, and Microsoft is gradually deprecating it. To delete resources as part of a deployment, use a deployment stack (
reference/arm/deployment-stacks.md).
6. Reference what already exists
resource kv 'Microsoft.KeyVault/vaults@2026-05-15' existing = {
name: 'kv-orders'
scope: resourceGroup('rg-shared')
}
output vaultUri string = kv.properties.vaultUri
Reference resources by symbol (kv.id, sa.properties.primaryEndpoints.blob) rather than building resourceId() strings: the symbol creates the dependency, and a string doesn't. Names and IDs must be computable at the start of the deployment. Using a runtime property as a name fails with BCP120.
Next
Failures: guides/arm/debug-failed-deployment.md. Compiler messages: reference/bicep/compile-errors.md. Making redeploys idempotent: patterns/bicep-idempotent-redeploy.md.