DeployIfNotExists (DINE) is one of the most powerful tools in Azure Policy. It can also be one of the fastest ways to create chaos: thousands of deployments, noisy failures, and a support queue that never clears.
This guide is how I build DINE policies so they scale: narrow detection, least-privilege identities, idempotent templates, and rollouts that start small and earn trust.
DINE in one minute
A DINE policy runs when a resource (or subscription) is created or updated. After a configurable delay, Azure Policy evaluates whether a related resource exists and matches the existenceCondition. If it doesn’t, Azure Policy triggers an ARM deployment using the managed identity on the policy assignment.
For existing resources, policy evaluation can mark them non-compliant without taking action. To bring existing resources into compliance, you use remediation tasks.
When to use DeployIfNotExists (and when not to)
Use DINE when:
You can safely apply a repeatable configuration after resource creation (diagnostic settings, encryption settings, agent onboarding, baseline alerts).
The desired state is expressible as an ARM template and is idempotent (running it twice causes no harm).
You can grant a managed identity the minimum permissions required to deploy that state.
Avoid DINE when:
You need to block creation immediately. Use Deny for hard guardrails.
You need to change properties on the evaluated resource in-place without deploying a related resource. Consider Modify (or Deny plus a paved road).
The deployment would reach outside the assignment scope, or require broad rights you don’t want policy to have.
Prereqs you should lock down first
A sandbox subscription to validate the policy and template with real resources.
A clear target scope: management group (preferred for scale), subscription, or resource group.
A managed identity plan: system-assigned identity per assignment, or a user-assigned identity you control.
RBAC: roles required by the template, mapped to roleDefinitionIds in the policy.
A rollout plan: start in audit / DoNotEnforce and expand by tiers, not all-at-once.
The anti-melt patterns (copy these)
1) Make existenceCondition specific
If your existenceCondition is vague, DINE will deploy over and over. Treat existenceCondition like a unit test. It should only be false when you truly need deployment.
2) Keep the deployment scope tight
Default to resource-group deployments. Only use subscription-scope deployments when you must. Subscription-scope deployments require a location in the deployment payload.
3) Parameterize the effect
Add an effect parameter so the same definition can run as Disabled, AuditIfNotExists, or DeployIfNotExists. That gives you a safe rollout switch without duplicating policy.
4) Use least-privilege roleDefinitionIds
DINE deployments run with the policy assignment’s managed identity. Grant only what the template needs, and nothing else.
5) Make templates idempotent
Use incremental mode. Avoid random names. Avoid deployments that create duplicates. If a related resource already exists, the template should converge, not fork.
6) Avoid nested deployment name collisions
If your template uses nested Microsoft.Resources/deployments, make nested names unique per evaluation to avoid contention.
7) Treat rollouts like production changes
Start small. Validate compliance results. Expand by tier. Turn on enforcement only when the signal is clean.
8) Plan an escape hatch
Policy exemptions are your pressure relief valve. Have a documented exemption path for legitimate exceptions, with expiry dates.
Fast path: start from a built-in DINE policy
If your control already exists as a built-in policy, don’t start from a blank file. Export the built-in definition, test it at a small scope, then clone it into a custom definition only if you need to.
CLI: find and inspect built-in policies
# Find built-in policy definitions by keyword (example: diagnostic settings)
az policy definition list --query "[?contains(displayName, 'Deploy') && contains(displayName, 'diagnostic')].{name:name, displayName:displayName}" -o table
# Show one definition (replace <POLICY_NAME>)
az policy definition show --name "<POLICY_NAME>" -o jsoncHow to build a DeployIfNotExists policy skeleton
Below is a skeleton you can adapt. It’s intentionally generic: swap in your resource types, your existenceCondition, and the deployment template.
Policy definition skeleton (JSON)
{
"properties": {
"displayName": "DINE Skeleton - <Your control here>",
"policyType": "Custom",
"mode": "Indexed",
"description": "Deploys a related resource/configuration when missing or misconfigured.",
"parameters": {
"effect": {
"type": "String",
"metadata": { "displayName": "Effect" },
"allowedValues": [ "Disabled", "AuditIfNotExists", "DeployIfNotExists" ],
"defaultValue": "AuditIfNotExists"
}
},
"policyRule": {
"if": {
"allOf": [
{ "field": "type", "equals": "<EVALUATED_RESOURCE_TYPE>" }
]
},
"then": {
"effect": "[parameters('effect')]",
"details": {
"type": "<RELATED_RESOURCE_TYPE>",
"existenceScope": "ResourceGroup",
"evaluationDelay": "AfterProvisioningSuccess",
"existenceCondition": {
"allOf": [
{ "field": "<RELATED_RESOURCE_FIELD_PATH>", "equals": "<DESIRED_VALUE>" }
]
},
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/<BUILTIN_OR_CUSTOM_ROLE_GUID>"
],
"deploymentScope": "ResourceGroup",
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"evaluatedResourceId": { "type": "string" }
},
"resources": [
{
"type": "<RELATED_RESOURCE_TYPE>",
"apiVersion": "<API_VERSION>",
"name": "<NAME_EXPRESSION>",
"properties": { }
}
]
},
"parameters": {
"evaluatedResourceId": { "value": "[field('id')]" }
}
}
}
}
}
}
}
}
What you replace in the skeleton:
<EVALUATED_RESOURCE_TYPE>: the thing you’re checking (example: Microsoft.KeyVault/vaults).
<RELATED_RESOURCE_TYPE>: the thing you’re deploying (example: Microsoft.Insights/diagnosticSettings
<RELATED_RESOURCE_FIELD_PATH>: the property that proves compliance.
<NAME_EXPRESSION>: how to name the related resource. Keep it deterministic.
<BUILTIN_OR_CUSTOM_ROLE_GUID>: the RBAC role ID your managed identity needs to run the deployment.
Role design: stop giving Contributor by default
DINE deployments run under the policy assignment’s managed identity. roleDefinitionIds should list the minimum roles required for the template to succeed. If you can’t name the minimum role, that’s a design smell.
CLI: role lookup + assignment
# Get the built-in role ID (example: Contributor)
az role definition list --name "Contributor" --query "[0].id" -o tsv
# Create a policy assignment with a system-assigned managed identity (example scope: management group)
az policy assignment create \
--name "dine-skeleton" \
--display-name "DINE Skeleton" \
--scope "/providers/Microsoft.Management/managementGroups/<MG_NAME>" \
--policy "<POLICY_DEFINITION_ID_OR_PATH>" \
--location "eastus" \
--mi-system-assignedScaling it across the estate
Once your skeleton works in a sandbox, scale using a pattern, not one-off assignments:
Wrap related DINE policies into an initiative (policy set) so you assign one thing per landing-zone tier.
Use parameters at the initiative level for shared values (workspace ID, resource group name, region allowlists).
Version your definitions and treat changes like releases. Policy changes can have blast radius.
Use progressive rollout: start in DoNotEnforce or Audit, validate compliance, then enable DeployIfNotExists and expand scope.
Automate the pipeline: policy-as-code repo, CI checks, and deployment stages.
Validation: how you know it’s working
1. Create a test resource that should trigger the policy.
2. Wait for evaluation and check compliance state for that resource.
3. In the policy assignment details, find the deployed resources or the related deployment record.
4. Confirm the related resource exists and matches the desired state.
5. Repeat with a second resource to confirm the deployment name logic doesn’t collide.

Top failure modes (and what to check first)
Managed identity missing permissions: Confirm the assignment has a managed identity and that role assignments exist at the right scope.
existenceCondition never matches: Verify the related resource type and field path. Start with a simple existence check, then tighten.
deploymentScope mismatch: If you set deploymentScope to Subscription, add a location in the deployment payload.
Policy scope too low for existenceScope: If existenceScope is Subscription, the assignment must be at subscription scope or higher.
Nested deployments collide: Make nested deployment names unique using deployment().name plus uniqueString().
Mode mismatch: Use Indexed for most resource evaluation. Use All only when you need it (and accept the extra evaluation).

Want the full policy skeleton pack?
Click here if you want the full pack (policy skeleton, initiative wrapper, assignment script, and a quick test checklist).