3.1. Bootstrap

In this lab you will provision the Azure prerequisites every participant needs before creating their Pulumi project: a Blob Storage backend for state and a Key Vault key for secret encryption.

Step 1 — Bootstrap the State Storage

The state backend must exist before Pulumi can use it, so it is created with the az CLI rather than Pulumi itself. Run the following commands to create a dedicated Resource Group, Storage Account, and Blob Container:

export NAME=<insert your name>   # must be unique per participant
export ACCOUNT=pulumi$RANDOM
az group create --location switzerlandnorth --name rg-pulumi-$NAME
az storage account create --name $ACCOUNT --resource-group rg-pulumi-$NAME

az role assignment create \
  --role "Storage Blob Data Contributor" \
  --assignee $(az ad signed-in-user show --query id -o tsv) \
  --scope $(az storage account show --name $ACCOUNT \
              --resource-group rg-pulumi-$NAME --query id -o tsv)

echo "Waiting for role assignment to propagate..."
until az storage container create --resource-group rg-pulumi-$NAME \
  --account-name $ACCOUNT --name pulumi-state \
  --public-access off --auth-mode login &>/dev/null; do
  sleep 10
done
echo "Container created."

Explanation

$RANDOM is a shell built-in that returns a random integer, giving the storage account a unique suffix without any extra tooling. Storage account names must be globally unique across all of Azure, so a random suffix is the simplest solution for a workshop environment.

Storage Blob Data Contributor grants blob data-plane access via Azure AD. Even a subscription Owner only has management-plane rights over storage accounts — blob read/write via --auth-mode login requires this explicit data-plane role.

--public-access off --auth-mode login means the container is private and access is controlled via Entra ID RBAC — no storage account keys are created or stored.

Troubleshooting: SubscriptionNotFound

If az storage account create fails with SubscriptionNotFound, there are two common causes:

1. Wrong tenant — your subscription may live in a different Azure AD tenant than your default login. Check which subscriptions are visible:

az account list --output table

If the target subscription is missing, re-login with its tenant:

az login --tenant <tenant-id>
az account set --subscription <subscription-id>

2. Storage provider not registered — new subscriptions don’t have all resource providers enabled by default. Register Microsoft.Storage and wait for it to complete:

az provider register --namespace Microsoft.Storage
az provider show --namespace Microsoft.Storage --query registrationState

Re-run the az storage account create command once the state shows Registered.

Why not create the storage account with Pulumi?

Pulumi needs a state backend before it can manage any resources. Using Pulumi to create its own backend is a chicken-and-egg problem — the az CLI bootstrap sidesteps it cleanly.


Step 2 — Bootstrap the Secret Provider

Pulumi encrypts secret config values at rest. The default is a passphrase, but for a team environment you want a cloud-managed key so every team member can decrypt secrets without sharing a passphrase. Create an Azure Key Vault and a key named pulumi that Pulumi will use as the encryption key:

export KEYVAULT=pulumi$RANDOM
az keyvault create \
  --resource-group rg-pulumi-$NAME \
  --name $KEYVAULT \
  --enable-rbac-authorization \
  --location switzerlandnorth

az role assignment create \
  --role "Key Vault Crypto Officer" \
  --assignee $(az ad signed-in-user show --query id -o tsv) \
  --scope $(az keyvault show --name $KEYVAULT \
              --resource-group rg-pulumi-$NAME --query id -o tsv)

echo "Waiting for role assignment to propagate..."
until az keyvault key create --vault-name $KEYVAULT --name pulumi &>/dev/null; do
  sleep 10
done
echo "Key created."

Explanation

$RANDOM gives the vault a unique suffix — Key Vault names must be globally unique across Azure, just like storage account names.

--enable-rbac-authorization disables the legacy access-policy model and enforces Entra ID RBAC for all data-plane operations — consistent with the storage account setup in Step 1. Crucially, this separates management-plane rights (create/configure the vault) from data-plane rights (use keys and secrets). Even a subscription Owner needs an explicit data-plane role assignment — the Key Vault Crypto Officer role grants the create/import/delete key operations Pulumi requires.

Pulumi uses the Azure Key Vault wrap/unwrap API to encrypt and decrypt secret values — it never stores the raw key material.

Troubleshooting: MissingSubscriptionRegistration

If az keyvault create fails with MissingSubscriptionRegistration, the Key Vault resource provider is not yet enabled on the subscription. Register it and wait for completion:

az provider register --namespace Microsoft.KeyVault --wait

Re-run the az keyvault create command once registration completes.

Why a dedicated key?

Using a Key Vault key instead of a passphrase means secrets are tied to your Azure identity. Access can be revoked per user via RBAC, rotated centrally, and audited through Key Vault diagnostic logs — none of which is possible with a shared passphrase.