---
name: azure-bicep-patterns
description: '**UTILITY SKILL** — Reusable Azure Bicep patterns: hub-spoke, private endpoints, diagnostics, AVM composition. WHEN: "hub-spoke Bicep", "private endpoint module", "diagnostic settings", "AVM Bicep composition". USE FOR: Bicep template design, hub-spoke networking, private endpoint patterns, AVM modules. DO NOT USE FOR: Terraform code (use terraform-patterns), architecture decisions (use azure-adr), troubleshooting, diagram generation (use drawio).'
compatibility: Requires Azure CLI with Bicep extension
---

# Azure Bicep Patterns Skill

Reusable infrastructure patterns for Azure Bicep templates. Complements
`iac-bicep-best-practices.instructions.md` (style) and `azure-defaults` skill (naming, tags, regions).

> **Canonical sources** — the security baseline, AVM-first mandate, naming
> conventions, required tags, and unique-suffix rule live in
> [`azure-defaults/SKILL.md`](../azure-defaults/SKILL.md) and
> [`iac-policy-compliance.md`](../../instructions/references/iac-policy-compliance.md).
> This skill restates the rules tersely below for IaC-output convenience
> only; in conflict, the canonical sources win.

## Quick Reference

| Pattern                  | When to Use                                      | Reference                                                              |
| ------------------------ | ------------------------------------------------ | ---------------------------------------------------------------------- |
| Hub-Spoke Networking     | Multi-workload environments with shared services | [hub-spoke-pattern](references/hub-spoke-pattern.md)                   |
| Private Endpoint Wiring  | Any PaaS service requiring private connectivity  | [private-endpoint-pattern](references/private-endpoint-pattern.md)     |
| Diagnostic Settings      | Every deployed resource (mandatory)              | [common-patterns](references/common-patterns.md)                       |
| Conditional Deployment   | Optional resources controlled by parameters      | [common-patterns](references/common-patterns.md)                       |
| Module Composition       | Breaking main.bicep into reusable modules        | [common-patterns](references/common-patterns.md)                       |
| Managed Identity Binding | Any service-to-service authentication            | [common-patterns](references/common-patterns.md)                       |
| Budget & Cost Monitoring | Every deployment (mandatory)                     | [budget-pattern](references/budget-pattern.md)                         |
| What-If / AVM Pitfalls   | Pre-deployment validation & AVM gotchas          | [avm-pitfalls](references/avm-pitfalls.md)                             |
| Batch Bicep Formatting   | After generating/editing the Bicep tree          | `npm run format:bicep -- infra/bicep/{project}` (wraps `bicep format`) |

## Canonical Example — Module Interface

Every Bicep module in this repo follows the same input/output contract:

- **Inputs (required)**: `name`, `location`, `tags`, `logAnalyticsWorkspaceName`
- **Outputs (required)**: `resourceId`, `resourceName`, `principalId` (use `.?principalId ?? ''` so modules without managed identity still expose the output)

Full code sample and rationale: [`references/module-interface.md`](references/module-interface.md).

## Steps

Applying a pattern in a Bicep template:

1. **Identify the pattern** — match your need to a row in [Quick Reference](#quick-reference) (hub-spoke, private endpoint, diagnostics, conditional, identity, budget)
2. **Load the reference** — read the linked `references/*.md` for the chosen pattern; do not load all at once
3. **Compose the module** — follow the Module Interface contract above (`name`/`location`/`tags`/`logAnalyticsWorkspaceName` in; `resourceId`/`resourceName`/`principalId` out)
4. **Pin AVM versions to the latest stable** — at plan time, query MCR (`https://mcr.microsoft.com/v2/bicep/avm/res/{path}/tags/list`) and pin the highest non-prerelease semver; never reuse a version from training data, a prior project, or `references/avm-modules.md`. Stale pins require a `pin_policy.mode = "exception"` block in `04-iac-contract.json` (see `azure-defaults` skill). Enforced by `npm run validate:avm-versions:freeze`.
5. **Add diagnostics + budget** — every deployed resource gets a diagnostic setting; every deployment gets a budget with 80%/100%/120% forecast alerts
6. **What-if before deploy** — run `az deployment group what-if` and review for unexpected deletes, SKU downgrades, or auth changes
7. **Validate** — `bicep build` + `bicep lint` + `npm run validate:iac-security-baseline`

## Rules

- **Hub-Spoke**: Hub holds shared infra; spokes peer to hub only; NSGs per subnet
- **Private Endpoints**: Always wire PE + DNS Zone Group + DNS Zone; see group ID table in reference
- **Diagnostics**: `categoryGroup: 'allLogs'` + `AllMetrics`; pass workspace **name** not ID
- **Conditional**: `bool` params with defaults; guard outputs with ternary
- **Identity**: `guid()` for idempotent role names; `principalType: 'ServicePrincipal'`; scope narrowly
- **Budget**: 3 forecast thresholds (80%/100%/120%); amount and emails MUST be parameters
- **What-If**: Run before every deploy; watch for unexpected deletes and SKU downgrades
- **AVM**: ALWAYS pin to the **latest published stable version** (resolve at plan time via MCR `tags/list`); wrap modules to override defaults; verify outputs in README. Stale pins require a `pin_policy` exception block — see `azure-defaults` skill.
- **AVM Version Source of Truth**: MCR tag listing (`mcr.microsoft.com/v2/bicep/{module}/tags/list`) is authoritative.
  Helpers and doc tables are NOT — they go stale. Validator: `npm run validate:avm-versions`.

## Gotchas

- **AVM output shapes vary across modules** — Different AVM modules expose different
  outputs. Always check the module README before referencing outputs.
- **Tag merging in AVM modules** — Some AVM modules merge tags internally.
  Verify deployed tags include all required policy tags after deployment.
- **What-If red flags** — Watch for unexpected deletes, SKU downgrades,
  public access changes, authentication mode changes, or identity removal.
  Always run what-if before deploy.
- **MCR version discovery** — When AVM version helpers are incomplete,
  query `mcr.microsoft.com/v2/bicep/{module}/tags/list` for authoritative
  published versions.
- **Cross-RG module `scope:` ARM ID split indexes** — Splitting a full
  resource ID (`/subscriptions/{sub}/resourceGroups/{rg}/providers/...`)
  on `/` yields `['', 'subscriptions', '{sub}', 'resourceGroups', '{rg}', ...]`.
  Subscription is at **index 2**, RG name is at **index 4**. Use:
  `scope: resourceGroup(split(resId, '/')[2], split(resId, '/')[4])`.
  Indexes `[1]`/`[3]` are the literals `'subscriptions'`/`'resourceGroups'`
  and only fail at `az deployment ... validate` time.
- **AVM `insights/metric-alert:0.4.1+` requires `criteria.allof[].name`** —
  Each entry in `criteria.allOf[]` needs a `name` field; omission passes
  `bicep build` but fails `az deployment ... validate`:
  ```bicep
  allOf: [{
    name: 'HighCpu'        // REQUIRED
    metricName: 'Percentage CPU'
    operator: 'GreaterThan'
    threshold: 80
    timeAggregation: 'Average'
  }]
  ```
- **`bicep build` poisons tree-hash folders** — Running `bicep build main.bicep`
  emits `main.json` next to the source. If the folder is hashed by
  `validate-iac-handoff.mjs`, that compiled output drifts the hash. The
  validator now excludes `<stem>.json` siblings of `<stem>.bicep`, but if
  you must build manually inside a handoff tree, `rm -f main.json`
  immediately after.
- **AKS `outboundType` must match the egress topology** — a NAT Gateway on the
  node subnet requires `userAssignedNATGateway`, NOT `userDefinedRouting` (UDR
  needs a 0.0.0.0/0 route table and is rejected when public network access is
  restricted). BYO-VNet clusters also need `Network Contributor` on the VNet, and
  fixed-size node pools must set `minCount`/`maxCount` to `null`. See
  [avm-pitfalls § AKS](references/avm-pitfalls.md#aks-managed-cluster-apply-time-gotchas).
- **MySQL Flexible Server: PE not delegated subnet; engine version 8.4** — on a
  shared PE subnet use `privateEndpoints[]` (not `delegatedSubnetResourceId`);
  pin `version: '8.4'` (latest GA LTS → 8.4.7), never the stale 8.0 or an
  innovation 9.x; `aad_auth_only` is read-only (enforce post-deploy). See
  [avm-pitfalls § MySQL](references/avm-pitfalls.md#mysql-flexible-server-apply-time-gotchas).

## Reference Index

| File                                                                  | Content                                                               |
| --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [hub-spoke-pattern.md](references/hub-spoke-pattern.md)               | Hub-spoke VNet orchestration with peering                             |
| [private-endpoint-pattern.md](references/private-endpoint-pattern.md) | PE wiring + DNS zone groups + group ID table                          |
| [common-patterns.md](references/common-patterns.md)                   | Diagnostics, conditional deploy, module composition, managed identity |
| [budget-pattern.md](references/budget-pattern.md)                     | Consumption budget, forecast alerts, anomaly detection                |
| [avm-pitfalls.md](references/avm-pitfalls.md)                         | What-if interpretation, AVM gotchas, learn more links                 |
| [module-interface.md](references/module-interface.md)                 | Canonical module input/output contract                                |

## Learn More

| Topic                | How to Find                                                                                          |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| AVM module catalog   | `mcp_azure-mcp_documentation` — `command: "microsoft_docs_search"`, `query: "Azure Verified Modules registry Bicep"`    |
| Resource type schema | `mcp_azure-mcp_documentation` — `command: "microsoft_docs_search"`, `query: "{resource-type} Bicep template reference"` |
