Choosing a Pulumi Azure Provider
Pulumi offers several packages for working with Azure. Rather than a single monolithic provider, the Azure ecosystem is divided along the lines of 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 available the same day Microsoft publishes them. 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
Providers
| 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) | Microsoft Graph API | 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. It is generated directly from the Azure Resource
Manager (ARM) OpenAPI specifications that Microsoft publishes, which means it achieves 100% API coverage
on the day new services and properties become available. Resources in Azure Native use PascalCase property
names that match the ARM API, 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-native versioning
The Azure Native provider follows semantic versioning and has gone through three major versions since its introduction. The current major version is v3, and all new projects should target it. Earlier major versions are still supported for existing users:
| Version | Status | Registry |
|---|---|---|
| v3 (current) | Actively developed | /registry/packages/azure-native/ |
| v2 | Supported, maintenance only | /registry/packages/azure-native-v2/ |
| v1 | Supported, maintenance only | Available via version selector on the registry page |
Major version upgrades are documented with migration guides on the registry page. Within a major version, upgrades are backward compatible and straightforward.
The Azure Classic provider
The Azure Classic provider (@pulumi/azure) is built on the Terraform
AzureRM provider via the Pulumi Terraform bridge. It was Pulumi’s original Azure provider, predating the
auto-generated Azure Native approach. Pulumi continues to support it for teams that have existing
infrastructure managed with Azure Classic, but it is not 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 is 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 used together with azure-native in almost every real-world Azure deployment,
because applications running on Azure infrastructure typically need service principals and managed identity
configurations. For example, an AKS cluster provisioned with azure-native commonly requires a service
principal or an Entra ID application registration, which you configure 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 is 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 and a Personal Access Token (PAT), supplied
via the AZDO_ORG_SERVICE_URL and AZDO_PERSONAL_ACCESS_TOKEN environment variables, or through the
Pulumi provider configuration.
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 (e.g.,
"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, just 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 with a very fast release cadence, 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 |
Next steps
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.