3.2. Project Setup
In this lab you will scaffold a new Pulumi project connected directly to the Azure backend provisioned in
3.1, and wire up the pulumi-azure-native provider.
Step 1 — Log in to Azure
Authenticate the Azure CLI — Pulumi reads credentials from this session automatically:
Confirm the active subscription:
You should see output similar to:
If multiple subscriptions are available, target the correct one:
Explanation
az login opens a browser or device-code flow and writes a token to ~/.azure/. Pulumi resolves Azure
credentials in this order:
| Source | When to use |
|---|---|
Active az CLI session | Local development — no extra config needed |
ARM_* environment vars | CI/CD pipelines and automation |
| Managed Identity | Azure-hosted compute (VMs, AKS nodes) |
| OIDC Workload Identity | GitHub Actions, Azure DevOps pipelines |
For this workshop the active az session is sufficient. No ARM_* variables need to be exported.
Why Owner permissions are required
Standard Contributor access is not enough for this workshop. Three operations require more:
| Operation | Why Contributor falls short |
|---|---|
pulumi login to Azure Blob Storage | Blob data-plane access is separate from control-plane RBAC — even Owners must explicitly hold Storage Blob Data Contributor on the storage account |
AKS assigns AcrPull to its managed identity | Creating role assignments requires Microsoft.Authorization/roleAssignments/write |
| ACR image push | AcrPush is a data-plane role not included in Contributor |
Each participant works in a dedicated sandbox subscription that contains only their own lab resources and is isolated from production. In this context, Owner is the pragmatic choice: it grants the necessary data-plane and role-assignment permissions without manual pre-provisioning for each participant.
Step 2 — Create the Project
Discover the backend resources created in Lab 3.1 and log in to the blob backend:
Create a new directory and scaffold the project with the Key Vault secrets provider:
When prompted:
| Prompt | Suggested value |
|---|---|
| Project description | (press Enter to accept the default) |
| Toolchain | pip |
Move stack config into a dedicated subdirectory:
Add stackConfigDir to Pulumi.yaml:
Verify Pulumi resolves the stack:
Explanation
By running pulumi login before pulumi new, the stack is created directly in Azure Blob Storage — no
local state is ever written. Passing --secrets-provider at project creation time means the stack is born
with Key Vault encryption; there is no passphrase prompt and no migration step later.
--name and --stack skip the interactive prompts for those fields so pulumi new only asks for the
project description and toolchain.
What to commit
Commit Pulumi.yaml, __main__.py, requirements.txt, and config/Pulumi.dev.yaml.
The scaffold already adds venv/ to .gitignore.
Step 3 — Fix the Subscription
Pin the Azure subscription ID so the provider always targets the correct subscription, regardless of which
account is active in the az session:
Confirm the subscription ID was written to the stack config:
Expected output:
Explanation
Without an explicit azure-native:subscriptionId, the provider falls back to whatever subscription the
az session currently has selected. If a team member switches subscription with az account set, the next
pulumi up targets a different subscription silently. Pinning it in config makes the target explicit and
auditable in version control.
$(az account show --query id -o tsv) reads the active subscription ID directly from the CLI — no
copy-paste required.
Step 4 — Set the Default Location
Configure the default Azure region so every resource uses it unless explicitly overridden:
Confirm both provider keys are present in the stack config:
Expected output:
Explanation
azure-native:location is a well-known config key read by the provider at deploy time. Setting it once
here means individual resource declarations do not need to repeat it — though they can override it when
needed.
| Region name | Location code |
|---|---|
| Switzerland North | switzerlandnorth |
| Switzerland West | switzerlandwest |
| West Europe | westeurope |
| North Europe | northeurope |
Step 5 — Create the Login Script
Create a helper script that re-establishes the backend connection whenever you open a new terminal:
Add the following content to scripts/pulumi_login.sh:
Run it to verify the connection:
Explanation
The script encodes the conventions of this workshop so there is nothing to copy-paste:
| Step | How |
|---|---|
| Subscription | Read from config/Pulumi.<stack>.yaml via yq |
az session | Switched to the correct subscription with az account set |
| Storage account | Discovered from rg-pulumi-$NAME via az storage account list |
| Backend URL | azblob://pulumi-state?storage_account=<account> — no env var needed |
| Stack | Selected (or created) with pulumi stack select --create |
Run pulumi_login.sh whenever you open a new terminal or switch stacks. Pulumi remembers the secrets
provider from the stack state — no extra flag is needed here.
Step 6 — Add the Azure Native Provider
Add pulumi-azure-native to requirements.txt and install it:
Explanation
pulumi-azure-native wraps the full Azure Resource Manager API surface — hundreds of namespaces, one per
Azure service. The namespaces used in this workshop are:
| Namespace | Azure service |
|---|---|
azure_native.resources | Resource Groups |
azure_native.network | VNet, Subnet, Public IP, DNS |
azure_native.containerservice | AKS clusters |
azure_native.containerregistry | Container Registry (ACR) |
azure_native.authorization | Role Assignments (RBAC) |