Azure Provider Setup
Why Azure with Pulumi?
The Problem: Azure's portal and ARM templates are verbose and error-prone. JSON-based templates lack loops, conditionals, and type checking.
The Solution: Pulumi gives you full programming language power to define Azure resources with IntelliSense, type safety, and reusable abstractions.
Real Impact: Teams migrating from ARM templates to Pulumi report 60% less code and 3x faster onboarding for new engineers.
Real-World Analogy
Think of Azure with Pulumi as a city planning system:
- Resource Groups = City districts that organize related buildings
- Virtual Networks = Road systems connecting buildings within a district
- VMs = Office buildings where work happens
- Azure Functions = On-demand meeting rooms you only pay for when used
- Blob Storage = Warehouses storing files and data
Key Azure Concepts in Pulumi
Resource Groups
Every Azure resource belongs to a resource group, which acts as a logical container for lifecycle and access management.
Azure Native vs Classic
Pulumi offers two Azure providers: azure-native (auto-generated, 100% API coverage) and classic (curated, stable).
Managed Identities
Use managed identities to authenticate between Azure services without storing credentials in code.
Location Strategy
Define a consistent region strategy using Pulumi config to deploy resources to the right Azure regions.
Installing the Azure Provider
# Create a new Pulumi project for Azure
pulumi new azure-typescript
# Install the Azure Native provider
npm install @pulumi/azure-native
# Configure Azure location
pulumi config set azure-native:location eastus
# Login with Azure CLI (required for auth)
az login
Resource Groups & VNets
Creating Resource Groups and VNets
import * as pulumi from "@pulumi/pulumi";
import * as resources from "@pulumi/azure-native/resources";
import * as network from "@pulumi/azure-native/network";
// Create a resource group
const resourceGroup = new resources.ResourceGroup("app-rg", {
location: "eastus",
});
// Create a virtual network
const vnet = new network.VirtualNetwork("app-vnet", {
resourceGroupName: resourceGroup.name,
addressSpace: { addressPrefixes: ["10.0.0.0/16"] },
});
// Public subnet
const publicSubnet = new network.Subnet("public-subnet", {
resourceGroupName: resourceGroup.name,
virtualNetworkName: vnet.name,
addressPrefix: "10.0.1.0/24",
});
// Private subnet
const privateSubnet = new network.Subnet("private-subnet", {
resourceGroupName: resourceGroup.name,
virtualNetworkName: vnet.name,
addressPrefix: "10.0.2.0/24",
});
// Network Security Group
const nsg = new network.NetworkSecurityGroup("web-nsg", {
resourceGroupName: resourceGroup.name,
securityRules: [{
name: "allow-http",
priority: 100,
direction: "Inbound",
access: "Allow",
protocol: "Tcp",
sourcePortRange: "*",
destinationPortRange: "80",
sourceAddressPrefix: "*",
destinationAddressPrefix: "*",
}],
});
Compute (VMs & Functions)
Creating a Virtual Machine
import * as compute from "@pulumi/azure-native/compute";
// Create a public IP
const publicIp = new network.PublicIPAddress("vm-pip", {
resourceGroupName: resourceGroup.name,
publicIPAllocationMethod: "Dynamic",
});
// Create a NIC
const nic = new network.NetworkInterface("vm-nic", {
resourceGroupName: resourceGroup.name,
ipConfigurations: [{
name: "ipconfig1",
subnet: { id: publicSubnet.id },
publicIPAddress: { id: publicIp.id },
}],
});
// Create a virtual machine
const vm = new compute.VirtualMachine("web-vm", {
resourceGroupName: resourceGroup.name,
networkProfile: {
networkInterfaces: [{ id: nic.id }],
},
hardwareProfile: { vmSize: "Standard_B2s" },
osProfile: {
computerName: "webserver",
adminUsername: "azureuser",
adminPassword: pulumi.secret("P@ssw0rd1234!"),
},
storageProfile: {
imageReference: {
publisher: "Canonical",
offer: "0001-com-ubuntu-server-jammy",
sku: "22_04-lts",
version: "latest",
},
osDisk: {
createOption: "FromImage",
managedDisk: { storageAccountType: "Standard_LRS" },
},
},
});
Creating Azure Functions
import * as web from "@pulumi/azure-native/web";
import * as storage from "@pulumi/azure-native/storage";
// Storage account for function app
const storageAccount = new storage.StorageAccount("funcsa", {
resourceGroupName: resourceGroup.name,
sku: { name: "Standard_LRS" },
kind: "StorageV2",
});
// App Service Plan (Consumption)
const plan = new web.AppServicePlan("func-plan", {
resourceGroupName: resourceGroup.name,
kind: "FunctionApp",
sku: { name: "Y1", tier: "Dynamic" },
});
// Function App
const functionApp = new web.WebApp("api-func", {
resourceGroupName: resourceGroup.name,
serverFarmId: plan.id,
kind: "FunctionApp",
siteConfig: {
appSettings: [
{ name: "FUNCTIONS_WORKER_RUNTIME", value: "node" },
{ name: "FUNCTIONS_EXTENSION_VERSION", value: "~4" },
{ name: "AzureWebJobsStorage", value: storageAccount.name.apply(
n => `DefaultEndpointsProtocol=https;AccountName=${n};...`
)},
],
},
});
export const functionUrl = functionApp.defaultHostName;
Storage & Databases
Blob Storage and CosmosDB
import * as documentdb from "@pulumi/azure-native/documentdb";
// Blob storage account
const dataStorage = new storage.StorageAccount("datasa", {
resourceGroupName: resourceGroup.name,
sku: { name: "Standard_GRS" },
kind: "StorageV2",
enableHttpsTrafficOnly: true,
});
// Blob container
const container = new storage.BlobContainer("uploads", {
resourceGroupName: resourceGroup.name,
accountName: dataStorage.name,
publicAccess: "None",
});
// CosmosDB account
const cosmosAccount = new documentdb.DatabaseAccount("cosmos-db", {
resourceGroupName: resourceGroup.name,
databaseAccountOfferType: "Standard",
locations: [{ locationName: "eastus", failoverPriority: 0 }],
consistencyPolicy: {
defaultConsistencyLevel: "Session",
},
});
// CosmosDB SQL database
const database = new documentdb.SqlResourceSqlDatabase("app-db", {
resourceGroupName: resourceGroup.name,
accountName: cosmosAccount.name,
resource: { id: "app-db" },
});
export const cosmosEndpoint = cosmosAccount.documentEndpoint;
Identity & Access
RBAC and Managed Identities
import * as authorization from "@pulumi/azure-native/authorization";
import * as managedidentity from "@pulumi/azure-native/managedidentity";
// User-assigned managed identity
const identity = new managedidentity.UserAssignedIdentity("app-identity", {
resourceGroupName: resourceGroup.name,
});
// Assign Storage Blob Data Contributor role
const roleAssignment = new authorization.RoleAssignment("blob-contributor", {
principalId: identity.principalId,
principalType: "ServicePrincipal",
roleDefinitionId: pulumi.interpolate`/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe`,
scope: dataStorage.id,
});
export const identityClientId = identity.clientId;
Quick Reference
Azure Resource Cheat Sheet
| Resource | Pulumi Module | Key Properties |
|---|---|---|
| Resource Group | resources.ResourceGroup |
location, tags |
| Virtual Network | network.VirtualNetwork |
addressSpace, resourceGroupName |
| Virtual Machine | compute.VirtualMachine |
vmSize, osProfile, storageProfile |
| Function App | web.WebApp |
serverFarmId, siteConfig, kind |
| Storage Account | storage.StorageAccount |
sku, kind, enableHttpsTrafficOnly |
| CosmosDB | documentdb.DatabaseAccount |
locations, consistencyPolicy |
| Managed Identity | managedidentity.UserAssignedIdentity |
resourceGroupName |
Azure CLI Authentication
Authentication Methods
az login- Interactive browser login for development- Service Principal - Set ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID env vars
- Managed Identity - Automatic when running on Azure VMs or App Service
- Azure CLI - Pulumi uses cached credentials from
az login