Bicep Key Vault Secrets

Passwords, connection strings, and keys must stay out of template files and source control. Azure Key Vault stores such secrets safely, and Bicep reads them at deployment time. The template carries a reference to the secret and never the secret itself.

Think of a hotel safe-deposit box. A guest hands the clerk a claim ticket instead of carrying cash around. Bicep holds the ticket, and Key Vault hands over the valuables only when the deployment needs them.

How the Secret Travels

  Bicep file ── "give me secret sqlPassword" ──►  Key Vault
       ▲                                              │
       │                                              │
       └────────── value (hidden from logs) ◄─────────┘
       │
       ▼
  SQL Server is created with the password

Prepare the Vault

The vault must allow template deployments to read its secrets. Enable the enabledForTemplateDeployment setting, and give the deploying identity permission to read secrets.

resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: 'kv-shop-001'
  location: 'eastus'
  properties: {
    tenantId: subscription().tenantId
    sku: {
      family: 'A'
      name: 'standard'
    }
    enabledForTemplateDeployment: true
    enableRbacAuthorization: true
  }
}

Read a Secret in a Parameter File

The az.getSecret function pulls a value from an existing vault. The parameter must carry the @secure() decorator in the template.

// main.bicep
@secure()
param sqlPassword string
// prod.bicepparam
using './main.bicep'

param sqlPassword = az.getSecret(
  '00000000-0000-0000-0000-000000000000',  // subscription ID
  'rg-security',                           // resource group of the vault
  'kv-shop-001',                           // vault name
  'sqlAdminPassword'                       // secret name
)

Pass a Secret to a Module

A template can point at an existing vault and hand a secret to a module. The getSecret method works only on secure module parameters.

resource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
  name: 'kv-shop-001'
  scope: resourceGroup('rg-security')
}

module sql 'sql.bicep' = {
  name: 'sqlDeploy'
  params: {
    adminPassword: kv.getSecret('sqlAdminPassword')
  }
}

Store a Secret with Bicep

A secret resource writes a value into the vault. The incoming value arrives through a secure parameter.

@secure()
param apiKey string

resource apiSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
  parent: kv
  name: 'apiKey'
  properties: {
    value: apiKey
  }
}

Rules That Protect Secrets

Mark Every Secret Parameter

Add @secure() to each parameter that holds a password, key, or token. Azure hides the value from deployment history and logs.

Keep Secrets Out of Outputs

Outputs appear in deployment history. Never return a password or key through an output.

Avoid Default Values

A default value sits in the file for everyone to read. Leave secure parameters without defaults.

Limit Access

Grant the deployment identity the smallest role that reads secrets. Review the vault's access list regularly.

Rotating a Secret

Changing a secret in Key Vault creates a new version. The following deployment reads the latest version, so the template needs no edit. Teams rotate passwords on a schedule without touching code.

Common Mistakes

Typing a password into a parameter file and committing it to a repository is the most frequent error. Anyone with repository access can read the value, and the history keeps it even after a delete. A second error is a vault without the template deployment setting, which causes the deployment to fail with an access message. A third error is a deployment identity without the secret-reading role. Check these three items first when a secret lookup fails.

Summary

Key Vault keeps secrets outside Bicep files. A secure parameter receives the value through az.getSecret in a parameter file or through getSecret on an existing vault resource. The vault must allow template deployments, outputs must never expose secrets, and rotation needs no template edit.