In module 3.1 you set up the Kladde’s subscription: budget, resource group rg-kladde-dev, tags, permissions and policies, with scripts for the Azure CLI and Azure PowerShell. That went quickly, but there’s a catch: the scripts describe steps, not the state. Whoever wants to know in half a year why the resource group looks exactly the way it does has to replay the scripts in their head and hope that nobody has changed anything in the portal since.

From now on, the Kladde’s house rules say: Infrastructure comes into being only as code. What was clicked in the portal is carried over into Bicep or deleted again. In this lesson you see why that’s more than a preference, and you write your first Bicep file.

Two ways to tell Azure something

Imagine the Kladde needs a storage account. With the Azure CLI, you write down what to do, step by step. That’s called imperative. A careful script for it looks roughly like this:

PowerShell
# scripts/storage-imperative.ps1 (for comparison only, don't run it)
$group = 'rg-kladde-dev'
$name = 'stkladdedevexample'

az storage account show --name $name --resource-group $group --output none 2>$null
if ($LASTEXITCODE -ne 0) {
    az storage account create --name $name --resource-group $group --sku Standard_LRS --kind StorageV2 `
        --min-tls-version TLS1_2 --allow-blob-public-access false
}
else {
    az storage account update --name $name --resource-group $group `
        --min-tls-version TLS1_2 --allow-blob-public-access false
}

Even with a single resource, the script has to ask whether it exists and know two paths: create or change. If a setting is added, it has to be added in both places. With twenty resources that depend on each other, this turns into a program that nobody likes to change anymore.

The other way is called declarative. You describe what the result should look like, and a tool finds out what to do to get there. The same storage account in Bicep:

bicep
resource storage 'Microsoft.Storage/storageAccounts@2026-04-01' = {
  name: 'stkladdedevexample'
  location: resourceGroup().location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
  }
}

There’s no if, no “create” and no “change.” If the account doesn’t exist yet, Azure creates it. If it already exists, Azure brings it in line with the description. At every point in time the file says what the infrastructure should look like, and that’s exactly what you can read and review in a pull request.