Choosing a Pulumi Azure Provider
Pulumi offers several packages for working with Azure. Rather than a single monolithic provider, the Azure ecosystem is organized around the underlying Azure APIs: one package for Azure infrastructure (Resource Manager), one for identity management (Microsoft Graph), one for DevOps platform resources, and a community-maintained package for low-level ARM access. This guide explains what each package does, how they compare, and which ones to use for your project.
@pulumi/azure-native).
It is auto-generated from the Azure Resource Manager (ARM) API specifications and covers 100% of ARM
resources, with new resources and API updates arriving in regular releases generated from those specifications. Use the
other providers alongside it to cover the parts of Azure that ARM does not manage, such as Entra ID
identities and Azure DevOps platform configuration.Packages at a glance
| Azure Native | Azure Classic | Azure AD / Entra ID | Azure DevOps | |
|---|---|---|---|---|
| Node.js | @pulumi/azure-native | @pulumi/azure | @pulumi/azuread | @pulumi/azuredevops |
| Python | pulumi-azure-native | pulumi-azure | pulumi-azuread | pulumi-azuredevops |
| Go | github.com/pulumi/pulumi-azure-native-sdk/... | github.com/pulumi/pulumi-azure/sdk/v6/go/azure | github.com/pulumi/pulumi-azuread/sdk/v6/go/azuread | github.com/pulumi/pulumi-azuredevops/sdk/v3/go/azuredevops |
| .NET | Pulumi.AzureNative | Pulumi.Azure | Pulumi.AzureAD | Pulumi.AzureDevOps |
| Java | com.pulumi/azure-native | com.pulumi/azure | com.pulumi/azuread | com.pulumi/azuredevops |
| Built on | ARM API specs (auto-generated) | Terraform AzureRM (bridged) | Terraform AzureAD (bridged), targeting Microsoft Graph API | Terraform Azure DevOps (bridged), targeting Azure DevOps REST API |
| Manages | All Azure cloud resources | Azure cloud resources (subset) | Entra ID users, groups, apps, SPs | Projects, repos, pipelines |
| Maintenance | Pulumi (official) | Pulumi (official) | Pulumi (official) | Pulumi (official) |
| Best for | All new Azure projects | Existing Azure Classic projects | Identity and access management | DevOps platform automation |
The Azure Native provider
The Azure Native provider (@pulumi/azure-native) is the recommended
choice for managing Azure cloud resources with Pulumi. Pulumi generates it directly from the Azure Resource
Manager (ARM) OpenAPI specifications that Microsoft publishes, which means it achieves 100% API coverage,
and new services and properties arrive with each regular release. Resource properties mirror the ARM API
schema, using each language’s naming convention, and each resource type can be pinned to a specific ARM API
version, giving you granular control over compatibility and upgrade cadence.
import * as resources from "@pulumi/azure-native/resources";
import * as network from "@pulumi/azure-native/network";
const rg = new resources.ResourceGroup("my-rg", {
location: "eastus",
});
const vnet = new network.VirtualNetwork("my-vnet", {
resourceGroupName: rg.name,
location: rg.location,
addressSpace: { addressPrefixes: ["10.0.0.0/16"] },
});
Azure API versions
Azure’s REST APIs are versioned per service, using date-based versions such as 2024-01-01 or
2025-04-01-preview. Azure Native selects a default API version for each resource, and that default is
what the SDKs expose and the API reference docs describe.
When you need a different API version, for example to pin one for extra stability, to stay on an older version you cannot migrate off yet, or to reach a preview feature, you can generate a local SDK for that exact version:
pulumi package add azure-native storage v20240101
Every API version Azure publishes is also reachable through the provider’s generic resource type. For how default versions are chosen and how both of these approaches work, see the Azure Native version guide.
The Azure Classic provider
The Azure Classic provider (@pulumi/azure) is built on the Terraform
AzureRM provider via the Pulumi Terraform bridge. Pulumi’s original Azure provider, it predates the
auto-generated Azure Native approach. Pulumi continues to support it for teams that have existing
infrastructure managed with Azure Classic, but it isn’t recommended for new projects.
Azure Classic covers a subset of Azure resources and receives new resources and API updates more slowly than Azure Native, since changes must flow through the upstream Terraform provider first. If you are currently using Azure Classic and want to migrate, see the migration guide from Azure Classic to Azure Native.
The Azure AD / Entra ID provider
The Azure Active Directory provider (@pulumi/azuread) manages identity
resources in Microsoft Entra ID (formerly known as Azure Active Directory). It’s a distinct provider from
azure-native because it targets the Microsoft Graph API rather than the Azure Resource Manager API.
Microsoft Graph is a separate API surface that governs directory objects — users, groups, application
registrations, service principals, conditional access policies, and other Entra ID constructs.
azuread name for backward compatibility, but the resources it manages are the same
as those in the Microsoft Entra admin center.The azuread provider is often used together with azure-native, because applications running on Azure
infrastructure frequently need service principals, application registrations, or group memberships. For
example, if an AKS cluster provisioned with azure-native uses a service principal instead of its default
system-assigned managed identity, you create that service principal and its Entra ID application registration
with azuread in the same Pulumi program.
import * as azuread from "@pulumi/azuread";
import * as resources from "@pulumi/azure-native/resources";
import * as containerservice from "@pulumi/azure-native/containerservice";
// Create an Entra ID application and service principal for AKS
const app = new azuread.Application("aks-app", {
displayName: "aks-service-principal",
});
const sp = new azuread.ServicePrincipal("aks-sp", {
clientId: app.clientId,
});
const spPassword = new azuread.ServicePrincipalPassword("aks-sp-password", {
servicePrincipalId: sp.id,
});
// Create an AKS cluster using the Azure Native provider
const rg = new resources.ResourceGroup("aks-rg", { location: "eastus" });
const cluster = new containerservice.ManagedCluster("aks-cluster", {
resourceGroupName: rg.name,
location: rg.location,
agentPoolProfiles: [{
count: 2,
vmSize: "Standard_DS2_v2",
name: "agentpool",
mode: "System",
}],
servicePrincipalProfile: {
clientId: app.clientId,
secret: spPassword.value,
},
dnsPrefix: "my-aks",
});
The azuread provider requires credentials to your Entra ID tenant. Configuration is typically shared
with azure-native via the common ARM_TENANT_ID, ARM_CLIENT_ID, and ARM_CLIENT_SECRET environment
variables, or via the Azure CLI (az login).
The Azure DevOps provider
The Azure DevOps provider (@pulumi/azuredevops) manages Azure DevOps
organizational resources via the Azure DevOps Service REST API. It’s independent of both ARM and Microsoft
Graph — it targets the DevOps platform itself rather than Azure cloud resources or identity objects.
Use the Azure DevOps provider to automate the configuration of your DevOps platform: creating projects, configuring Git repositories, defining build pipelines, managing service connections, setting branch policies, and administering agent pools. This is useful in platform engineering contexts where teams want to treat DevOps configuration as code and manage it with the same tools they use to manage their cloud infrastructure.
import * as azuredevops from "@pulumi/azuredevops";
const project = new azuredevops.Project("my-project", {
name: "my-project",
visibility: "private",
versionControl: "Git",
workItemTemplate: "Agile",
});
const repo = new azuredevops.Git("my-repo", {
projectId: project.id,
name: "my-repo",
initialization: {
initType: "Clean",
},
});
const pipeline = new azuredevops.BuildDefinition("my-pipeline", {
projectId: project.id,
name: "CI Pipeline",
repository: {
repoId: repo.id,
repoType: "TfsGit",
ymlPath: "azure-pipelines.yml",
},
});
The Azure DevOps provider requires an organization service URL, supplied via the AZDO_ORG_SERVICE_URL
environment variable or the Pulumi provider configuration. For authentication it accepts a Personal Access
Token (PAT) via the AZDO_PERSONAL_ACCESS_TOKEN environment variable, and also supports service principals,
OIDC, managed identity, and the Azure CLI.
The community AzAPI package
The azapi package is a community-maintained provider built by
@dirien on top of the Terraform AzAPI provider. It provides a
generic approach to ARM resource management using JSON body definitions and ARM type strings (for example,
"Microsoft.Web/serverfarms@2020-06-01"), without requiring typed resource classes for each resource.
This approach is analogous to using raw ARM templates or making direct REST calls, but with Pulumi’s dependency graph and state management layered on top.
azapi package is community-maintained, not an official Pulumi product. It is published under
the @ediri/ package namespace (not @pulumi/). The source repository was archived in July 2025,
and its README now recommends using the Azure Native provider instead. For most use cases, the Azure
Native provider is the better choice: it is Pulumi-maintained, auto-generated from the same ARM
specifications, and provides typed, Pulumi-idiomatic resources for every ARM resource type.A scenario where azapi has historically been useful is accessing a brand-new or preview ARM resource
type before Pulumi has published an updated azure-native release. In practice, because azure-native
auto-generates from the ARM spec and releases regularly, this gap is rarely significant.
Composing providers in a single program
In most real-world Azure deployments, you will use azure-native and azuread together. Occasionally
you will add azuredevops as well if you are also automating DevOps platform configuration. Pulumi
programs support any number of providers simultaneously, and provider credentials are configured
independently so that each provider can authenticate to its respective API surface.
The following example shows a complete pattern combining azure-native and azuread:
import * as pulumi from "@pulumi/pulumi";
import * as azuread from "@pulumi/azuread";
import * as resources from "@pulumi/azure-native/resources";
// Create a resource group using the Azure Native provider.
const resourceGroup = new resources.ResourceGroup("my-resource-group", {
location: "eastus",
});
// Create an Entra ID application registration using the Azure AD provider.
const application = new azuread.Application("my-application", {
displayName: "my-application",
});
// Create a service principal for the application.
const servicePrincipal = new azuread.ServicePrincipal("my-service-principal", {
clientId: application.clientId,
});
export const resourceGroupName = resourceGroup.name;
export const applicationClientId = application.clientId;
export const servicePrincipalId = servicePrincipal.id;
import pulumi
import pulumi_azuread as azuread
import pulumi_azure_native.resources as resources
# Create a resource group using the Azure Native provider.
resource_group = resources.ResourceGroup("my-resource-group",
location="eastus",
)
# Create an Entra ID application registration using the Azure AD provider.
application = azuread.Application("my-application",
display_name="my-application",
)
# Create a service principal for the application.
service_principal = azuread.ServicePrincipal("my-service-principal",
client_id=application.client_id,
)
pulumi.export("resourceGroupName", resource_group.name)
pulumi.export("applicationClientId", application.client_id)
pulumi.export("servicePrincipalId", service_principal.id)
package main
import (
"github.com/pulumi/pulumi-azure-native-sdk/resources/v3"
"github.com/pulumi/pulumi-azuread/sdk/v6/go/azuread"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create a resource group using the Azure Native provider.
resourceGroup, err := resources.NewResourceGroup(ctx, "my-resource-group", &resources.ResourceGroupArgs{
Location: pulumi.String("eastus"),
})
if err != nil {
return err
}
// Create an Entra ID application registration using the Azure AD provider.
application, err := azuread.NewApplication(ctx, "my-application", &azuread.ApplicationArgs{
DisplayName: pulumi.String("my-application"),
})
if err != nil {
return err
}
// Create a service principal for the application.
servicePrincipal, err := azuread.NewServicePrincipal(ctx, "my-service-principal", &azuread.ServicePrincipalArgs{
ClientId: application.ClientId,
})
if err != nil {
return err
}
ctx.Export("resourceGroupName", resourceGroup.Name)
ctx.Export("applicationClientId", application.ClientId)
ctx.Export("servicePrincipalId", servicePrincipal.ID())
return nil
})
}
using System.Collections.Generic;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
using AzureAD = Pulumi.AzureAD;
return await Deployment.RunAsync(() =>
{
// Create a resource group using the Azure Native provider.
var resourceGroup = new AzureNative.Resources.ResourceGroup("my-resource-group", new()
{
Location = "eastus",
});
// Create an Entra ID application registration using the Azure AD provider.
var application = new AzureAD.Application("my-application", new()
{
DisplayName = "my-application",
});
// Create a service principal for the application.
var servicePrincipal = new AzureAD.ServicePrincipal("my-service-principal", new()
{
ClientId = application.ClientId,
});
return new Dictionary<string, object?>
{
["resourceGroupName"] = resourceGroup.Name,
["applicationClientId"] = application.ClientId,
["servicePrincipalId"] = servicePrincipal.Id,
};
});
package myproject;
import com.pulumi.Pulumi;
import com.pulumi.azurenative.resources.ResourceGroup;
import com.pulumi.azurenative.resources.ResourceGroupArgs;
import com.pulumi.azuread.Application;
import com.pulumi.azuread.ApplicationArgs;
import com.pulumi.azuread.ServicePrincipal;
import com.pulumi.azuread.ServicePrincipalArgs;
public class App {
public static void main(String[] args) {
Pulumi.run(ctx -> {
// Create a resource group using the Azure Native provider.
var resourceGroup = new ResourceGroup("my-resource-group", ResourceGroupArgs.builder()
.location("eastus")
.build());
// Create an Entra ID application registration using the Azure AD provider.
var application = new Application("my-application", ApplicationArgs.builder()
.displayName("my-application")
.build());
// Create a service principal for the application.
var servicePrincipal = new ServicePrincipal("my-service-principal", ServicePrincipalArgs.builder()
.clientId(application.clientId())
.build());
ctx.export("resourceGroupName", resourceGroup.name());
ctx.export("applicationClientId", application.clientId());
ctx.export("servicePrincipalId", servicePrincipal.id());
});
}
}
name: azure-providers-yaml
description: An example that uses the Azure Native and Azure AD providers together to create a resource group and an Entra ID service principal.
runtime: yaml
config:
pulumi:tags:
value:
pulumi:template: azure-yaml
resources:
# Create a resource group using the Azure Native provider.
my-resource-group:
type: azure-native:resources:ResourceGroup
properties:
location: eastus
# Create an Entra ID application registration using the Azure AD provider.
my-application:
type: azuread:Application
properties:
displayName: my-application
# Create a service principal for the application.
my-service-principal:
type: azuread:ServicePrincipal
properties:
clientId: ${my-application.clientId}
outputs:
resourceGroupName: ${my-resource-group.name}
applicationClientId: ${my-application.clientId}
servicePrincipalId: ${my-service-principal.id}
Choosing the right provider
The following table summarizes which provider to use for common Azure tasks:
| Task | Provider |
|---|---|
| Create a resource group, VNet, VM, storage account, AKS cluster, etc. | azure-native |
| Create Entra ID users, groups, or administrative units | azuread |
| Register an application in Entra ID | azuread |
| Create a service principal for a workload | azuread |
| Manage role assignments and RBAC | azure-native (Authorization module) |
| Create Azure DevOps projects and repositories | azuredevops |
| Define build pipelines and agent pools | azuredevops |
| Manage service connections in Azure DevOps | azuredevops |
| Migrate from Azure Classic to Azure Native | See the migration guide |