---
cuppy_hub: azure
cuppy_path: patterns/bicep-idempotent-redeploy.md
cuppy_section: docs
cuppy_doc: W6T3-4PCG
cuppy_doc_uuid: 0f0b7e64-554b-4691-8369-8fbdfa0a2c89
cuppy_permalink: https://cuppy.io/azure/d/W6T3-4PCG
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/azure/docs/patterns/bicep-idempotent-redeploy
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 2b0eee6d0472a3b3a0fb50847bbd6ac585820e387913cbfdb3499c055c524249
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Bicep that redeploys cleanly

**A Bicep file is idempotent when deploying it twice changes nothing the second time.** Incremental mode makes that possible but doesn't guarantee it: it reapplies **every property of every resource in the file**, so anything the file omits or computes differently each run is reset, renamed or refused.

## Known-good

```bicep
param location string = resourceGroup().location
param appPrincipalId string

var suffix = uniqueString(resourceGroup().id)
var secretsUserRoleId = '4633458b-17de-408a-b874-0445c86b69e6'

resource vnet 'Microsoft.Network/virtualNetworks@2026-01-01' = {
  name: 'vnet-${suffix}'
  location: location
  properties: {
    addressSpace: { addressPrefixes: ['10.20.0.0/16'] }
    subnets: [ // subnets inside the VNet, not as child resources
      { name: 'apps', properties: { addressPrefix: '10.20.1.0/24' } }
      { name: 'data', properties: { addressPrefix: '10.20.2.0/24' } }
    ]
  }
}

resource kv 'Microsoft.KeyVault/vaults@2026-05-15' = {
  name: take('kv-${suffix}', 24)
  location: location
  properties: {
    tenantId: subscription().tenantId
    sku: { family: 'A', name: 'standard' }
    enableRbacAuthorization: true // explicit: the default depends on the API version
    enablePurgeProtection: true
  }
}

resource kvRead 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(kv.id, appPrincipalId, secretsUserRoleId)
  scope: kv
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', secretsUserRoleId)
    principalId: appPrincipalId
    principalType: 'ServicePrincipal'
  }
}

output keyVaultUri string = kv.properties.vaultUri // an address, not a secret
```

And in the pipeline:

- `bicep build` fails the job on `BCP035`/`036`/`037`/`081` warnings.
- `what-if` runs and is reviewed before `create`.
- Each run uses a unique deployment name (with a timestamp), or one name per environment that never runs twice at once.
- Deletions go through a deployment stack (`reference/arm/deployment-stacks.md`).

## Known-bad

| Configuration | What happens |
|---|---|
| Subnets declared as separate `Microsoft.Network/virtualNetworks/subnets` resources, VNet redeployed without them | The VNet's `subnets` is reapplied as empty: subnets are removed, or the deploy fails with `InUseSubnetCannotBeDeleted` if something uses them |
| A property set by hand in the portal, absent from the file | Reset to its default on the next deploy (incremental mode reapplies, it doesn't merge) |
| Role assignment named from `newGuid()` (a parameter default) or a hard-coded GUID | `newGuid()`: each run asks for the same principal, role and scope under a new name → `RoleAssignmentExists` (409). Hard-coded GUID with a recreated identity: `RoleAssignmentUpdateNotPermitted` |
| Key Vault without `enableRbacAuthorization`, API version moved from `2025-05-01` to `2026-05-15` | **New** vaults become RBAC vaults and the template's `accessPolicies` are ignored: 403 `ForbiddenByRbac` for every consumer. Existing vaults keep their model |
| `--mode Complete` to delete removed resources | Deletes everything in the group that isn't in the file, including resources other teams deployed; `DeploymentFailedCleanUp` when it lacks rights. Microsoft is deprecating complete mode |
| Resource names from `newGuid()` or `utcNow()` parameter defaults | A new resource every run; the old ones stay |
| An API version Bicep has no types for (`BCP081`) | No property checking: typos deploy and fail in Azure, or silently do nothing |
| Secrets in `output` (keys, connection strings) | Stored in plain text in deployment history, readable by anyone with read access to the group. The linter warns: `outputs-should-not-contain-secrets` |
| One fixed deployment name used by parallel pipelines | `DeploymentActiveAndUneditable` |
| `dependsOn` lists copied everywhere | Slower deployments, circular dependencies, and job-size limits on large loops |

## Which side is wrong

- **The second deploy fails, the first worked:** look for a name or property computed differently per run (`newGuid`, `utcNow`, a changed principal) or a concurrent deployment.
- **The second deploy succeeds but something broke:** a property or child resource the file doesn't declare was reset. Diff `what-if` output from the run before.
- **Works in one subscription, fails in another:** providers, quotas, policy or region (`reference/arm/errors.md`), not the file.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 107 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/azure/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
