Let’s face it — hardcoding passwords into templates is like taping your house key to the front door. This exercise shows how to do it the secure way.


🚀 What We're Building

You're now upgrading your Bicep deployment from “just works” to “production-friendly.” That means:

  • No more hardcoding sensitive values
  • Centralizing secrets in Azure Key Vault
  • Using parameter files to stay clean, consistent, and secure across environments

In short:

We’re teaching our Bicep template to “read the room” — or at least read the values — from a secure source 🎯


🛠 Step 1: Prep Your Template (Bicep File)

🧹 First, remove the default SKU from your appServicePlanSku

@description('The name and tier of the App Service plan SKU.')
param appServicePlanSku object

Why? Because we’ll define it in a parameter file (great for dev/test/prod flexibility).


✍️ Step 2: Add Secure Parameters

Next, let’s add two new secure parameters for a SQL Server:

@secure()
@description('The administrator login username for the SQL server.')
param sqlServerAdministratorLogin string

@secure()
@description('The administrator login password for the SQL server.')
param sqlServerAdministratorPassword string

And finally, one for the database SKU:

@description('The name and tier of the SQL database SKU.')
param sqlDatabaseSku object

⚠️ Pro Tip:

We don’t set default values for passwords or usernames. We want these provided securely.


🧮 Step 3: Add Variables

These help us build consistent names dynamically:

var appServicePlanName = '${environmentName}-${solutionName}-plan'
var appServiceAppName = '${environmentName}-${solutionName}-app'
var sqlServerName = '${environmentName}-${solutionName}-sql'
var sqlDatabaseName = 'Employees'

🧱 Step 4: Add the SQL Resources

At the bottom of your main.bicep, define the SQL server and DB:

resource sqlServer 'Microsoft.Sql/servers@2024-05-01-preview' = {
  name: sqlServerName
  location: location
  properties: {
    administratorLogin: sqlServerAdministratorLogin
    administratorLoginPassword: sqlServerAdministratorPassword
  }
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2024-05-01-preview' = {
  parent: sqlServer
  name: sqlDatabaseName
  location: location
  sku: {
    name: sqlDatabaseSku.name
    tier: sqlDatabaseSku.tier
  }
}

📁 Step 5: Create a Parameter File

Now let’s offload the actual values into a file.

🗂 Create: main.parameters.dev.json

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "appServicePlanSku": {
      "value": {
        "name": "F1",
        "tier": "Free"
      }
    },
    "sqlDatabaseSku": {
      "value": {
        "name": "Standard",
        "tier": "Standard"
      }
    }
  }
}

Save it. 🎉


🔑 Step 6: Deploy and Prompt for Sensitive Info

Run:

az login
az group create --name BicepRG --location westus

az deployment group create \
  --resource-group BicepRG \
  --name main \
  --template-file main.bicep \
  --parameters main.parameters.dev.json

You’ll be prompted to manually enter:

  • SQL Admin Login
  • SQL Admin Password

Azure will validate them. Make sure:

  • Login is not admin, root, or something basic
  • Password meets complexity rules

🏰 Step 7: Enter the Castle — Azure Key Vault

Now we create a key vault to hold our secrets 🔐

keyVaultName='your-unique-kv-name'

read -s -p "Enter the login name: " login
read -s -p "Enter the password: " password

az keyvault create \
  --name $keyVaultName \
  --resource-group BicepRG \
  --location westus \
  --enabled-for-template-deployment true

⚠️ If you get a “not authorized” error — go to Azure Portal and assign yourself the Key Vault Secrets Officer role.

Then, store your secrets:

az keyvault secret set --vault-name $keyVaultName --name "sqlServerAdministratorLogin" --value $login --output none
az keyvault secret set --vault-name $keyVaultName --name "sqlServerAdministratorPassword" --value $password --output none

🧭 Step 8: Get the Key Vault ID

az keyvault show --name $keyVaultName --query id --output tsv

It’ll look like:

/subscriptions/xxx/resourceGroups/BicepRG/providers/Microsoft.KeyVault/vaults/your-kv-name

🔄 Step 9: Update Parameter File with Secret References

Back in main.parameters.dev.json, append your Key Vault references:

"sqlServerAdministratorLogin": {
  "reference": {
    "keyVault": {
      "id": "YOUR-KEY-VAULT-RESOURCE-ID"
    },
    "secretName": "sqlServerAdministratorLogin"
  }
},
"sqlServerAdministratorPassword": {
  "reference": {
    "keyVault": {
      "id": "YOUR-KEY-VAULT-RESOURCE-ID"
    },
    "secretName": "sqlServerAdministratorPassword"
  }
}

✏️ Be careful with spelling — I accidentally wrote adminstratiorLogin and wasted 10 mins wondering what went wrong 😅


🧪 Step 10: Final Deployment (Fully Secure!)

az deployment group create \
  --resource-group BicepRG \
  --name main \
  --template-file main.bicep \
  --parameters main.parameters.dev.json

✅ This time, Azure pulls the secrets from the Key Vault — no prompts. Everything is safe, clean, and automated.


🔍 Validate in the Azure Portal

  • Go to Resource Groups > BicepRG
  • Select the latest Deployment
  • Under Inputs, you’ll see all your parameters (except the secure ones — those are hidden)
  • Check the SQL Server + Database resources too!

🧠 In Short

Concept What It Means
Parameter Files Store your config externally for cleaner templates
@secure() Hide sensitive values like passwords from the UI/logs
Key Vault Central vault for secrets, with permissions + audit trails
Reference() Use Key Vault secrets without exposing them directly
RBAC Fix If blocked, assign yourself the Key Vault Secrets Officer role

Wanna follow my Azure learning journey?

Stick around — I’m sharing it all, wins and stumbles included 😄

You can find me on LinkedIn — drop me a message and just say hi 👋

Would love to hear what you're working on or learning!