Skip to main content
Pulumi logo Pulumi logo
  1. Docs
  2. Infrastructure as Code
  3. Guides
  4. Clouds
  5. Azure

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.

    For all new Azure infrastructure projects, use the Azure Native provider (@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 NativeAzure ClassicAzure AD / Entra IDAzure DevOps
    Node.js@pulumi/azure-native@pulumi/azure@pulumi/azuread@pulumi/azuredevops
    Pythonpulumi-azure-nativepulumi-azurepulumi-azureadpulumi-azuredevops
    Gogithub.com/pulumi/pulumi-azure-native-sdk/...github.com/pulumi/pulumi-azure/sdk/v6/go/azuregithub.com/pulumi/pulumi-azuread/sdk/v6/go/azureadgithub.com/pulumi/pulumi-azuredevops/sdk/v3/go/azuredevops
    .NETPulumi.AzureNativePulumi.AzurePulumi.AzureADPulumi.AzureDevOps
    Javacom.pulumi/azure-nativecom.pulumi/azurecom.pulumi/azureadcom.pulumi/azuredevops
    Built onARM API specs (auto-generated)Terraform AzureRM (bridged)Terraform AzureAD (bridged), targeting Microsoft Graph APITerraform Azure DevOps (bridged), targeting Azure DevOps REST API
    ManagesAll Azure cloud resourcesAzure cloud resources (subset)Entra ID users, groups, apps, SPsProjects, repos, pipelines
    MaintenancePulumi (official)Pulumi (official)Pulumi (official)Pulumi (official)
    Best forAll new Azure projectsExisting Azure Classic projectsIdentity and access managementDevOps 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.

    Microsoft rebranded Azure Active Directory (Azure AD) to Microsoft Entra ID in 2023. The Pulumi package retains the 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.

    The 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:

    TaskProvider
    Create a resource group, VNet, VM, storage account, AKS cluster, etc.azure-native
    Create Entra ID users, groups, or administrative unitsazuread
    Register an application in Entra IDazuread
    Create a service principal for a workloadazuread
    Manage role assignments and RBACazure-native (Authorization module)
    Create Azure DevOps projects and repositoriesazuredevops
    Define build pipelines and agent poolsazuredevops
    Manage service connections in Azure DevOpsazuredevops
    Migrate from Azure Classic to Azure NativeSee the migration guide

    Next steps

      The infrastructure as code platform for any cloud.