agentleFS
timothywarner-orgtimothywarner-org/claude-code/.claude/skills/azure-bicep-skill/SKILL.md

azure-bicep-skill

Skill223 starsChanged 4 months agoMITView on GitHub

Use when authoring, reviewing, or refactoring Azure Bicep code. Triggers on Bicep module design, Azure Verified Modules (AVM) selection, landing zone IaC, .bicepparam parameter files, what-if deployment validation, bicepconfig.json linting, GitHub Actions Bicep CI with OIDC, Key Vault secret references, and any decision about Bicep file structure, naming, tagging, or scope (resourceGroup, subscription, managementGroup, tenant).

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 3 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not trigger for: pure ARM JSON work (recommend converting to Bicep first), Terraform-only workflows, Azure portal click-ops walkthroughs.

    line 21
  2. Very specific:

    Never accept a secret as a plain parameter.

    line 73
  3. Very specific:

    Follow Cloud Adoption Framework naming: <resource-type>-<workload>-<env>-<region>-<instance>.

    line 97
  4. Very specific:

    Always declare targetScope at the top of the file.

    line 116
  5. Very specific:

    Read every Modify, Delete, and DeployIgnored line.

    line 161
  6. Distinctive:

    When generating or reviewing Bicep, walk this loop:

    line 165
  7. Very specific:

    Parameter audit: Every param has @description.

    line 169
  8. Very specific:

    Identity audit: No service principal secrets.

    line 170
  9. Very specific:

    Validation: Run bicep build (syntax), bicep lint (style, with bicepconfig.json), and az deployment <scope> what-if (deployment diff).

    line 173
  10. Very specific:

    CI integration: Ensure the change is reachable from a GitHub Actions workflow that uses OIDC, runs what-if, requires manual approval for …

    line 174
  11. Distinctive:

    When generating Bicep for this environment, assume:

    line 239

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Azure Bicep Skill

Microsoft's proven-practice guidance for production Bicep deployments, distilled. Use this skill whenever Bicep code is being authored, reviewed, or refactored, or when picking between custom modules and Azure Verified Modules.

When to use

Trigger this skill when you see any of the following:

  • A .bicep or .bicepparam file being created, edited, or reviewed
  • A request involving "landing zone," "AVM," "Azure Verified Module," "what-if," or "bicepconfig"
  • A question about Bicep scopes (targetScope), parameter typing, or output design
  • A GitHub Actions workflow that deploys Bicep
  • A choice between Bicep and Terraform (Bicep is the default for Tim's stack; only deviate with explicit justification)
  • A custom module being proposed when an AVM module exists for the same resource

Do not trigger for: pure ARM JSON work (recommend converting to Bicep first), Terraform-only workflows, Azure portal click-ops walkthroughs.

Core principles

1. AVM first, custom second

Azure Verified Modules are Microsoft-published, WAF-aligned, versioned Bicep modules in the public Bicep Registry. They have built-in support for diagnostics, managed identities, private endpoints, RBAC assignments, and tagging. Prefer them over hand-rolled modules.

Why: AVM modules are maintained by the product groups, tested across regions, and updated for breaking API changes. Reinventing them is technical debt you will pay forever.

// Preferred: AVM resource module, version-pinned
module storage 'br/public:avm/res/storage/storage-account:0.14.3' = {
  name: 'storage-deployment'
  params: {
    name: storageAccountName
    location: location
    skuName: 'Standard_GRS'
    tags: tags
  }
}

See references/AVM-GUIDE.md for the module catalog, the resource (res) vs pattern (ptn) distinction, and version-pinning strategy.

2. Strong types and decorators on every param

Bicep parameters should be self-documenting and self-validating.

@description('Cost center for chargeback. Must match Finance master list.')
@minLength(4)
@maxLength(8)
param costCenter string

@description('Environment tier. Drives SKU selection and retention policies.')
@allowed([
  'dev'
  'test'
  'prod'
])
param environment string

@description('Admin password. Sourced from Key Vault via getSecret().')
@secure()
param sqlAdminPassword string

Why: Decorators turn deployment-time mistakes into authoring-time errors. @allowed is your cheapest policy enforcement.

3. Secrets come from Key Vault, never parameters

Never accept a secret as a plain parameter. Reference an existing Key Vault and pull the secret via getSecret():

resource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
  name: keyVaultName
  scope: resourceGroup(keyVaultRgName)
}

module sql 'br/public:avm/res/sql/server:0.10.0' = {
  name: 'sql-deployment'
  params: {
    name: sqlServerName
    location: location
    administratorLoginPassword: kv.getSecret('sql-admin-password')
  }
}

Why: Secret values passed as parameters end up in deployment history, CLI scrollback, and CI logs. getSecret() keeps them inside the ARM control plane only.

See references/SECURITY.md for managed identity patterns and OIDC federation.

4. CAF naming and mandatory tags

Follow Cloud Adoption Framework naming: <resource-type>-<workload>-<env>-<region>-<instance>. Use uniqueString() only for globally-scoped resources where collisions are real (storage accounts, Key Vaults, App Service plans hosting custom domains).

var namePrefix = 'kv-${workload}-${environment}-${location}'
var keyVaultName = '${namePrefix}-${take(uniqueString(resourceGroup().id), 4)}'

var mandatoryTags = {
  Environment: environment
  CostCenter: costCenter
  Owner: ownerEmail
  Workload: workload
  ManagedBy: 'Bicep'
}

See references/NAMING-AND-TAGGING.md for the full CAF table and tag governance.

5. Explicit targetScope, even when it's the default

Always declare targetScope at the top of the file. The default is resourceGroup, but stating it removes ambiguity for reviewers and prevents accidental scope mismatch when modules are reused.

targetScope = 'subscription'

@description('Landing zone resource group name.')
param resourceGroupName string

resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
  name: resourceGroupName
  location: location
  tags: mandatoryTags
}

6. Symbolic references over dependsOn

Bicep infers dependencies from symbolic references. Manual dependsOn arrays are usually a sign of a missing reference or a circular design.

// Preferred: dependency is implicit via storage.outputs.resourceId
module diagnostics 'modules/diagnostics.bicep' = {
  name: 'diag-deployment'
  params: {
    targetResourceId: storage.outputs.resourceId
    logAnalyticsWorkspaceId: laws.outputs.resourceId
  }
}

Why: Explicit dependsOn masks the actual data flow and breaks when modules are reordered or refactored.

7. Outputs are the contract, never the secret store

Outputs are read by humans and by downstream modules. They appear in deployment history. Output resource IDs, names, and endpoints. Never output a secret value. If a caller needs a secret, give them the Key Vault URI and let them call getSecret().

8. what-if before create, always

az deployment sub what-if `
  --location eastus2 `
  --template-file main.bicep `
  --parameters main.bicepparam

Read every Modify, Delete, and DeployIgnored line. what-if is the closest thing Bicep has to a dry-run, and it catches drift, accidental deletes, and parameter mistakes before they become incidents.

Workflow

When generating or reviewing Bicep, walk this loop:

  1. Scope check. Confirm targetScope matches the intended deployment scope. Subscription scope for landing zones, resourceGroup for workload deployments.
  2. AVM check. For every resource being created, search the AVM registry. If a module exists, use it. If it doesn't, document why a custom module is needed.
  3. Parameter audit. Every param has @description. Strings have @allowed or length constraints where the value space is bounded. Secrets are @secure() and sourced from Key Vault.
  4. Identity audit. No service principal secrets. Managed identities for service-to-service auth. Key Vault role assignments via AVM module's roleAssignments parameter.
  5. Naming and tags. CAF naming convention. Mandatory tag set applied at the resource-group or subscription level and inherited.
  6. Output audit. Outputs are minimal, named, typed, and contain no secret values.
  7. Validation. Run bicep build (syntax), bicep lint (style, with bicepconfig.json), and az deployment <scope> what-if (deployment diff).
  8. CI integration. Ensure the change is reachable from a GitHub Actions workflow that uses OIDC, runs what-if, requires manual approval for prod, and posts the diff to the PR.

Validation commands

Preferred: run all four gates at once via the bundled script.

# All four gates: build + lint + AVM version-pin scan + what-if
./scripts/validate-bicep.ps1 `
  -TemplateFile main.bicep `
  -ParametersFile main.bicepparam `
  -Scope subscription `
  -Location eastus2

# Local authoring loop (skip the tenant call)
./scripts/validate-bicep.ps1 `
  -TemplateFile main.bicep `
  -ParametersFile main.bicepparam `
  -SkipWhatIf

The scripts/validate-bicep.ps1 script wraps the four mandatory pre-deploy gates (build, lint, AVM version-pin scan, what-if) and exits non-zero on any failure. Use it as the local pre-commit check and as the CI gate. It is the executable counterpart of this skill's guidance.

Individual gates (when you need to run one in isolation):

# Syntax + transpile check
bicep build main.bicep

# Lint with project rules
bicep lint main.bicep

# Subscription-scope what-if
az deployment sub what-if `
  --location eastus2 `
  --template-file main.bicep `
  --parameters main.bicepparam

# Subscription-scope deploy (only after what-if review)
az deployment sub create `
  --location eastus2 `
  --template-file main.bicep `
  --parameters main.bicepparam

# PSRule for Azure (policy compliance, complementary to the gates above)
Invoke-PSRule -InputPath . -Module 'PSRule.Rules.Azure'

Anti-patterns to flag

Anti-patternWhy it's wrongWhat to do instead
Hardcoded subscription ID or tenant IDBreaks portability, leaks tenant info into sourceUse subscription().subscriptionId and tenant().tenantId
Inline secret in parameter fileSecret hits git history and CI logsKey Vault reference via getSecret()
dependsOn arrayHides real data flow, breaks on refactorSymbolic reference (module.outputs.x)
Custom module for a resource AVM coversReinventing maintained codeUse the AVM module, pin the version
@secure() missing on secret paramsSecret value rendered in deployment historyAdd @secure(), source from Key Vault
Outputting a secretSecret appears in deployment outputsOutput the Key Vault URI instead
JSON ARM where Bicep would workLoses type safety, decorators, lintingConvert with bicep decompile then refactor
No targetScope declarationAmbiguous deployment scopeDeclare it explicitly at the top of the file
uniqueString() on every nameNames become unreadable in the portalUse only for globally-scoped resources
Service principal with client secretLong-lived credential, rotation burdenManaged identity, or OIDC federation for CI

Tim's stack defaults

When generating Bicep for this environment, assume:

  • IaC: Bicep first. Terraform only when Bicep cannot do the job (multi-cloud, third-party providers).
  • CI/CD: GitHub Actions in timothywarner-org, OIDC federated credentials, no stored secrets.
  • Identity: Entra ID. Managed identities (system-assigned preferred, user-assigned when shared across resources).
  • Secrets: Azure Key Vault, referenced via getSecret() and managed identity.
  • Compute targets: Azure Container Apps, AKS, Functions, App Service.
  • Data targets: Cosmos DB, Azure SQL, Storage Account with hierarchical namespace, Azure AI Search.
  • Region default: eastus2 unless specified.
  • Tagging: Environment, CostCenter, Owner, Workload, ManagedBy are mandatory.

Deviation from these defaults requires an explicit justification in the response.

Bundled scripts

  • scripts/validate-bicep.ps1 - Runs all four pre-deploy gates (build, lint, AVM version-pin scan, what-if) in one shot. PowerShell 7+. Use locally before commit and in CI as a gate. See the Validation commands section above for invocation examples.

Reference files

  • references/AVM-GUIDE.md - Azure Verified Modules catalog, registry naming, version-pinning strategy

Additional reference files (NAMING-AND-TAGGING.md, SECURITY.md, CI-CD.md) and starter templates under assets/templates/ may be added as the skill matures. Until then, follow the inline guidance in this SKILL.md and the AVM guide.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.