Skip to content
Back to skills

Iac Bicep

ASecurity

Bicep / ARM discipline for any project where Bicep is the active IaC tool — modules/environments/main.bicep layout, parameter files (.bicepparam), the az validate → what-if flow, deployment scopes, and the hard-won Azure Bicep gotchas (globally-unique names, ACR SKU, PgBouncer Burstable limit, alert module location, KQL interpolation, metric names). Activate when working on *.bicep / *.bicepparam files, az deployment / azd provision, ARM deployment failures, or diagnosing Bicep validation and...

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 2, 2026
ai-agentsgobashsqlexpressazure

Works with

  • cli

Security analysis

A100/100

Scanned September 2, 2026

npx -y skills add eric-sabe/engsys --skill iac-bicep --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Iac Bicep?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Iac Bicep
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/eric-sabe-iac-bicep/badge)](https://www.skillsdirectory.com/skills/eric-sabe-iac-bicep)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: iac-bicep
description: Bicep / ARM discipline for any project where Bicep is the active IaC tool — modules/environments/main.bicep layout, parameter files (.bicepparam), the az validate → what-if flow, deployment scopes, and the hard-won Azure Bicep gotchas (globally-unique names, ACR SKU, PgBouncer Burstable limit, alert module location, KQL interpolation, metric names). Activate when working on *.bicep / *.bicepparam files, az deployment / azd provision, ARM deployment failures, or diagnosing Bicep validation and drift.
---

# Bicep / ARM Discipline

The operational discipline for Bicep as the active IaC tool (Azure). Pairs with the
`cloud-architecture-azure` pack (service-level detail) and the
`azure-deployment-preflight` skill (the pre-deploy gate). Project file layout and
resource-group/subscription facts come from `CLAUDE.md`.

## Core stance

- **Infrastructure is software.** If it only works once, it doesn't work. "Just deploy it
  again" is not a strategy — understand *why* it failed.
- **`bicep build` only checks syntax.** It does NOT catch invalid property combinations,
  metric names, KQL scope, secret-ref mismatches, or naming collisions. The real gate is
  `az deployment group validate` + `what-if` (see `azure-deployment-preflight`).
- **Batch your fixes.** Each push triggers a long CI run — read the whole failing module,
  fix every issue, push once. One run per problem cluster, not one per error message.

## Project layout

A conventional, recreatable structure (confirm exact paths in `CLAUDE.md`):

```
infrastructure/
  main.bicep                 # orchestration: wires modules together, declares params
  modules/                   # reusable modules, one concern each (db, acr, alerts, …)
    <concern>.bicep
  environments/
    dev.bicepparam           # per-env parameter files (preferred over .parameters.json)
    staging.bicepparam
    prod.bicepparam
  scripts/                   # helper scripts (e.g. seed-keyvault.sh)
```

- **`main.bicep`** is the orchestrator: declare `param`s, set defaults, instantiate
  modules with explicit dependencies, expose `output`s.
- **Modules** group related resources with a narrow, typed interface (`param` +
  `@description`). Don't wrap a single resource unless it earns reuse.
- **`.bicepparam`** files (typed, support expressions and `getSecret()`) are preferred
  over JSON parameter files. Keep one per environment; never hard-code per-env values in
  `main.bicep`.

## Deployment scopes

The `targetScope` declaration picks the deploy/validate command:

| `targetScope` | command family |
| --- | --- |
| `resourceGroup` (default) | `az deployment group ...` |
| `subscription` | `az deployment sub ... --location <loc>` |
| `managementGroup` | `az deployment mg ... --management-group-id <id> --location <loc>` |
| `tenant` | `az deployment tenant ... --location <loc>` |

## The validate → what-if flow (run before every deploy)

```bash
cd infrastructure
bicep build main.bicep --stdout            # 1. syntax only

az deployment group validate \             # 2. real deploy-time validation
  --resource-group <rg> \
  --template-file main.bicep \
  --parameters environments/<env>.bicepparam \
  --parameters postgresAdminPassword="dummy" --parameters postgresAdminUsername="dummy"

az deployment group what-if \              # 3. the preview (creates/modifies/DELETES)
  --resource-group <rg> \
  --template-file main.bicep \
  --parameters environments/<env>.bicepparam \
  --validation-level Provider              # fall back to ProviderNoRbac on RBAC errors
```

**If it fails locally, fix it locally.** Don't push and let CI discover it. Review the
what-if for **deletes/replacements of stateful resources** (PostgreSQL, Key Vault,
storage). See `azure-deployment-preflight` for stale-deployment cleanup and the full gate.

## Secrets

- No secrets in templates or param files. Reference **Key Vault** from `.bicepparam` via
  `getSecret()` and from app resources as secret references.
- Prefer **managed identities** for resource-to-resource auth over connection strings.

## Hard-won Bicep gotchas (don't rediscover the expensive way)

- **Globally-unique names** (Key Vault, storage, ACR, Redis, Front Door) collide or are
  soft-deleted from prior attempts. Parameterize the name (`param keyVaultName` etc.) and
  override in `*.bicepparam` with a short unique suffix — don't hard-code the bare default.
  If a helper script (Key Vault seeder) defaults the name internally, **pass the override
  explicitly** and ensure every workflow step uses the same resolved name.
- **ACR SKU:** `Basic` may be unavailable on some subscriptions; `Standard` works but
  `retentionPolicy` requires `Premium` — remove it from dev/staging. If a failed deploy
  left a broken ACR, create it manually and let Bicep treat it as no-change.
- **PgBouncer not on Burstable:** Burstable (`Standard_B*`) PostgreSQL can't run PgBouncer
  (needs GeneralPurpose+). Guard with `= if (currentSku.tier != 'Burstable') { ... }`.
- **Alert module location:** `metricAlerts` → `location: 'global'`; `scheduledQueryRules`
  → real region (NOT `global`), and they must scope to the **Log Analytics workspace ID**,
  not the App Insights ID (the `AppRequests` table lives in the workspace).
- **KQL in verbatim strings doesn't interpolate:** `${vars}` inside `'''...'''` are NOT
  substituted — build the query with string-concatenation variables.
- **Metric names:** PostgreSQL Flexible Server uses `active_connections`, not
  `connection_percent` (that's Azure SQL).

## Drift & troubleshooting

- ARM tracks deployments by name; a **failed sub-deployment blocks re-deploy**
  (`DeploymentActive`) even while `Failed` — clean it up (see preflight Step 4).
- Bicep/ARM is declarative-incremental by default (Complete mode deletes anything not in
  the template — use with care). What-if before every deploy surfaces out-of-band drift.
- No click-ops in production; manual changes create snowflakes that the next deploy fights.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.