1. Docs
  2. Clouds
  3. Azure
  4. Get started

Get started with Pulumi & Azure

    Pulumi’s infrastructure-as-code SDK helps you create, deploy, and manage Azure containers, serverless functions, and infrastructure using familiar programming languages.

    This tutorial takes you through the steps to easily deploy a static website.

    Install Pulumi

    $ brew install pulumi/tap/pulumi
    $ curl -fsSL https://get.pulumi.com | sh
    > choco install pulumi
    All Windows examples in this tutorial assume you are running in PowerShell.

    Or explore more installation options.

    Install language runtime

    Install Node.js.

    Install Python version 3.7 or later. To reduce potential issues with setting up your Python environment on Windows or macOS, you should install Python through the official Python installer.

    pip is required to install dependencies. If you installed Python from source, with an installer from python.org, or via Homebrew you should already have pip. If Python is installed using your OS package manager, you may have to install pip separately, see Installing pip/setuptools/wheel with Linux Package Managers. For example, on Debian/Ubuntu you must run sudo apt install python3-venv python3-pip.

    If you're having trouble setting up Python on your machine, see Python 3 Installation & Setup Guide for detailed installation instructions on various operating systems and distributions.

    Install Go.

    Pulumi requires a supported version of Go— this typically refers to the two most recent minor releases. If you're using Linux, your distribution may not provide an up to date version of the Go compiler. To check what version of Go you have installed, use: go version.

    Install .NET SDK.

    Pulumi will need the dotnet executable in order to build and run your Pulumi .NET application. Ensure that the dotnet executable can be found on your path after installation.

    Install Java 11 or later and Apache Maven 3.6.1 or later.

    Pulumi will need the java, javac, and mvn executables in order to build and run your Pulumi Java application. Ensure that these executables can be found on your path after installation.

    Good news! You don’t have to install anything else to write Pulumi programs in YAML.

    Configure Pulumi to access Microsoft Azure

    Pulumi requires cloud credentials to manage and provision resources. Pulumi can authenticate to Azure using a user account or service principal that has programmatic access with rights to deploy and manage your Azure resources.

    Pulumi relies on the Azure SDK to authenticate requests from your computer to Azure. Your credentials are never sent to pulumi.com.

    In this guide, you will need a user account with permissions to create and populate Blob storage containers and provide anonymous access to a Blob file.

    When developing locally, we recommend that you install the Azure CLI and then authorize access with a user account.

    az login
    
    The Azure CLI, and Pulumi, will use the default subscription for the account. Change the active subscription with the az account set command.

    Create project

    Let’s create your first Pulumi project, stack, and program. Pulumi projects and stacks organize Pulumi code. Projects are similar to GitHub repos and stacks are an instance of code with separate configuration. Projects can have multiple stacks for different development environments or for different cloud configurations.

    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-typescript
    
    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-python
    
    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-csharp
    
    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-go
    
    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-java
    
    $ mkdir quickstart && cd quickstart
    $ pulumi new azure-yaml
    

    The pulumi new command creates a Pulumi project with basic scaffolding.

    You will be asked for a project name and project description.

    This command will walk you through creating a Pulumi project.
    
    Enter a value or leave blank to accept the (default), and press <ENTER>.
    Press ^C at any time to quit.
    
    project name: (quickstart)
    project description: (A minimal Azure Native Pulumi program)
    Created project 'quickstart'
    

    Then you will be asked for a stack name.

    Please enter your desired stack name.
    To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
    stack name: (dev)
    Created stack 'dev'
    

    Finally, you will be prompted for a configuration value for the stack, specifically the Azure location.

    azure-native:location: The Azure location to use: (WestUS2)
    Saved config
    

    After the command completes, the project and stack will be ready.

    Review project

    Let’s review some of the generated project files:

    • Pulumi.yaml defines both the project and the program that manages your stack resources.
    • src/main/java/myproject defines the project’s Java package root.
    • index.js index.ts main.py main.go Program.cs Program.fs Program.vb App.java Pulumi.yaml is the Pulumi program that defines your stack resources.

    Let’s examine index.js index.ts __main__.py main.go Program.cs Program.fs Program.vb App.java Pulumi.yaml .

    import * as pulumi from "@pulumi/pulumi";
    import * as resources from "@pulumi/azure-native/resources";
    import * as storage from "@pulumi/azure-native/storage";
    
    // Create an Azure Resource Group
    const resourceGroup = new resources.ResourceGroup("resourceGroup");
    
    // Create an Azure resource (Storage Account)
    const storageAccount = new storage.StorageAccount("sa", {
        resourceGroupName: resourceGroup.name,
        sku: {
            name: storage.SkuName.Standard_LRS,
        },
        kind: storage.Kind.StorageV2,
    });
    
    // Export the primary key of the Storage Account
    const storageAccountKeys = storage.listStorageAccountKeysOutput({
        resourceGroupName: resourceGroup.name,
        accountName: storageAccount.name
    });
    
    export const primaryStorageKey = storageAccountKeys.keys[0].value;
    
    """An Azure RM Python Pulumi program"""
    
    import pulumi
    from pulumi_azure_native import storage
    from pulumi_azure_native import resources
    
    # Create an Azure Resource Group
    resource_group = resources.ResourceGroup("resource_group")
    
    # Create an Azure resource (Storage Account)
    account = storage.StorageAccount(
        "sa",
        resource_group_name=resource_group.name,
        sku=storage.SkuArgs(
            name=storage.SkuName.STANDARD_LRS,
        ),
        kind=storage.Kind.STORAGE_V2,
    )
    
    # Export the primary key of the Storage Account
    primary_key = (
        pulumi.Output.all(resource_group.name, account.name)
        .apply(
            lambda args: storage.list_storage_account_keys(
                resource_group_name=args[0], account_name=args[1]
            )
        )
        .apply(lambda accountKeys: accountKeys.keys[0].value)
    )
    
    pulumi.export("primary_storage_key", primary_key)
    
    package main
    
    import (
        "github.com/pulumi/pulumi-azure-native/sdk/go/azure/resources"
    	"github.com/pulumi/pulumi-azure-native/sdk/go/azure/storage"
        "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
    )
    
    func main() {
        pulumi.Run(func(ctx *pulumi.Context) error {
            // Create an Azure Resource Group
            resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
            if err != nil {
                return err
            }
    
            // Create an Azure resource (Storage Account)
            account, err := storage.NewStorageAccount(ctx, "sa", &storage.StorageAccountArgs{
    			ResourceGroupName: resourceGroup.Name,
    			AccessTier:        storage.AccessTierHot,
    			Sku: &storage.SkuArgs{
    				Name: storage.SkuName_Standard_LRS,
    			},
    			Kind: storage.KindStorageV2,
            })
            if err != nil {
                return err
            }
    
            // Export the primary key of the Storage Account
    		ctx.Export("primaryStorageKey", pulumi.All(resourceGroup.Name, account.Name).ApplyT(
    			func(args []interface{}) (string, error) {
    				resourceGroupName := args[0].(string)
    				accountName := args[1].(string)
    				accountKeys, err := storage.ListStorageAccountKeys(ctx, &storage.ListStorageAccountKeysArgs{
    					ResourceGroupName: resourceGroupName,
    					AccountName:       accountName,
    				})
    				if err != nil {
    					return "", err
    				}
    
    				return accountKeys.Keys[0].Value, nil
    			},
    		))
    
            return nil
        })
    }
    
    using Pulumi;
    using Pulumi.AzureNative.Resources;
    using Pulumi.AzureNative.Storage;
    using Pulumi.AzureNative.Storage.Inputs;
    using System.Collections.Generic;
    
    return await Deployment.RunAsync(() =>
    {
        // Create an Azure Resource Group
        var resourceGroup = new ResourceGroup("resourceGroup");
    
        // Create an Azure resource (Storage Account)
        var storageAccount = new StorageAccount("sa", new StorageAccountArgs
        {
            ResourceGroupName = resourceGroup.Name,
            Sku = new SkuArgs
            {
                Name = SkuName.Standard_LRS
            },
            Kind = Kind.StorageV2
        });
    
        var storageAccountKeys = ListStorageAccountKeys.Invoke(new ListStorageAccountKeysInvokeArgs
        {
            ResourceGroupName = resourceGroup.Name,
            AccountName = storageAccount.Name
        });
    
        var primaryStorageKey = storageAccountKeys.Apply(accountKeys =>
        {
            var firstKey = accountKeys.Keys[0].Value;
            return Output.CreateSecret(firstKey);
        });
    
        // Export the primary key of the Storage Account
        return new Dictionary<string, object?>
        {
            ["primaryStorageKey"] = primaryStorageKey
        };
    });
    
    package myproject;
    
    import com.pulumi.Pulumi;
    import com.pulumi.azurenative.resources.ResourceGroup;
    import com.pulumi.azurenative.storage.StorageAccount;
    import com.pulumi.azurenative.storage.StorageAccountArgs;
    import com.pulumi.azurenative.storage.StorageFunctions;
    import com.pulumi.azurenative.storage.enums.Kind;
    import com.pulumi.azurenative.storage.enums.SkuName;
    import com.pulumi.azurenative.storage.inputs.ListStorageAccountKeysArgs;
    import com.pulumi.azurenative.storage.inputs.SkuArgs;
    import com.pulumi.core.Either;
    import com.pulumi.core.Output;
    import com.pulumi.deployment.InvokeOptions;
    
    public class App {
        public static void main(String[] args) {
            Pulumi.run(ctx -> {
                var resourceGroup = new ResourceGroup("resourceGroup");
                var storageAccount = new StorageAccount("sa", StorageAccountArgs.builder()
                        .resourceGroupName(resourceGroup.name())
                        .sku(SkuArgs.builder()
                                .name(SkuName.Standard_LRS)
                                .build())
                        .kind(Kind.StorageV2)
                        .build());
    
                var primaryStorageKey = getStorageAccountPrimaryKey(
                        resourceGroup.name(),
                        storageAccount.name());
    
                ctx.export("primaryStorageKey", primaryStorageKey);
            });
        }
    
        private static Output<String> getStorageAccountPrimaryKey(Output<String> resourceGroupName,
                                                                  Output<String> accountName) {
            return Output.tuple(resourceGroupName, accountName).apply(tuple -> {
                var actualResourceGroupName = tuple.t1;
                var actualAccountName = tuple.t2;
                var invokeResult = StorageFunctions.listStorageAccountKeys(ListStorageAccountKeysArgs.builder()
                        .resourceGroupName(actualResourceGroupName)
                        .accountName(actualAccountName)
                        .build(), InvokeOptions.Empty);
                return Output.of(invokeResult)
                        .applyValue(r -> r.keys().get(0).value())
                        .asSecret();
            });
        }
    }
    
    name: quickstart
    runtime: yaml
    description: A minimal Azure Native Pulumi YAML program
    resources:
      resourceGroup:
        type: azure-native:resources:ResourceGroup
      sa:
        type: azure-native:storage:StorageAccount
        properties:
          resourceGroupName: ${resourceGroup.name}
          sku:
            name: Standard_LRS
          kind: StorageV2
    variables:
      storageAccountKeys:
        Fn::Invoke:
          Function: azure-native:storage:listStorageAccountKeys
          Arguments:
            resourceGroupName: ${resourceGroup.name}
            accountName: ${sa.name}
    outputs:
      primaryStorageKey: ${storageAccountKeys.keys[0].value}
    

    This Pulumi program creates an Azure resource group and storage account and then exports the storage account’s primary key.

    In this program, the location of the resource group is set in the configuration setting azure-native:location (check the Pulumi.dev.yaml file). This is an easy way to set a global location for your program so you don’t have to specify the location for each resource manually. The location for the storage account is automatically derived from the location of the resource group. To override the location for a resource, set the location property to one of Azure’s supported locations.

    Deploy stack

    Let’s deploy the stack:

    $ pulumi up
    

    This command evaluates the program and determines what resources need updates. A preview is shown that outlines the changes that will be made when you run the update:

    Previewing update (dev):
    
        Type                                              Name             Plan
     +   pulumi:pulumi:Stack                              quickstart-dev   create
     +   ├─ azure-native:resources:ResourceGroup          resourceGroup    create
     +   └─ azure-native:storage:StorageAccount           sa               create
    
    Resources:
        + 3 to create
    
    Do you want to perform this update?
      yes
    > no
      details
    

    Once the preview has finished choose yes to create your new storage account in Azure.

    
    Do you want to perform this update? yes
    Updating (dev):
    
         Type                                             Name             Status
     +   pulumi:pulumi:Stack                              quickstart-dev   created
     +   ├─ azure-native:resources:ResourceGroup          resourceGroup    created
     +   └─ azure-native:storage:StorageAccount           sa               created
    
    Outputs:
        primaryStorageKey: "<key_value>"
    
    Resources:
        + 3 created
    
    Duration: 26s
    
    $ pulumi stack output primaryStorageKey
    
    $ pulumi stack output primary_storage_key
    
    $ pulumi stack output primaryStorageKey
    
    $ pulumi stack output primaryStorageKey
    
    $ pulumi stack output primaryStorageKey
    
    $ pulumi stack output primaryStorageKey
    

    Running that command will print out the storage account’s primary key.

    Modify program

    Now that your storage account is provisioned, let’s add an object to it. In the project directory, create a new index.html file with some content in it.

    cat <<EOT > index.html
    <html>
        <body>
            <h1>Hello, Pulumi!</h1>
        </body>
    </html>
    EOT
    
    cat <<EOT > index.html
    <html>
        <body>
            <h1>Hello, Pulumi!</h1>
        </body>
    </html>
    EOT
    
    @"
    <html>
      <body>
        <h1>Hello, Pulumi!</h1>
      </body>
    </html>
    "@ | Out-File -FilePath index.html
    

    Now that you have your new index.html with some content, you can enable static website support, upload index.html to a storage container, and retrieve a public URL through the use of resource properties. These properties can be used to define dependencies between related resources or to retrieve property values for further processing.

    To start, open index.ts and add the following right after the storage account creation:

    // Enable static website support
    const staticWebsite = new storage.StorageAccountStaticWebsite("staticWebsite", {
        accountName: storageAccount.name,
        resourceGroupName: resourceGroup.name,
        indexDocument: "index.html",
    });
    

    To start, open __main__.py and add the following right after the storage account creation:

    # Enable static website support
    static_website = storage.StorageAccountStaticWebsite("staticWebsite",
        account_name=account.name,
        resource_group_name=resource_group.name,
        index_document="index.html")
    

    To start, open main.go and add the following right after the storage account creation:

    // Enable static website support
    staticWebsite, err := storage.NewStorageAccountStaticWebsite(ctx, "staticWebsite", &storage.StorageAccountStaticWebsiteArgs{
        AccountName:       account.Name,
        ResourceGroupName: resourceGroup.Name,
        IndexDocument:     pulumi.String("index.html"),
    })
    if err != nil {
        return err
    }
    

    To start, open Program.cs and add the following right after the storage account creation:

    // Enable static website support
    var staticWebsite = new StorageAccountStaticWebsite("staticWebsite", new StorageAccountStaticWebsiteArgs
    {
        AccountName = storageAccount.Name,
        ResourceGroupName = resourceGroup.Name,
        IndexDocument = "index.html",
    });
    

    To start, open App.java and add the following imports:

    import com.pulumi.azurenative.storage.StorageAccountStaticWebsite;
    import com.pulumi.azurenative.storage.StorageAccountStaticWebsiteArgs;
    import com.pulumi.azurenative.storage.Blob;
    import com.pulumi.azurenative.storage.BlobArgs;
    import com.pulumi.azurenative.storage.outputs.EndpointsResponse;
    import com.pulumi.asset.FileAsset;
    

    Next, add the following right after the storage account creation:

    var staticWebsite = new StorageAccountStaticWebsite("staticWebsite",
                        StorageAccountStaticWebsiteArgs.builder()
                                .accountName(storageAccount.name())
                                .resourceGroupName(resourceGroup.name())
                                .indexDocument("index.html")
                                .build());
    

    To start, open Pulumi.yaml and add the following right after the storage account creation:

    resources:
      # ...
      staticWebsite:
        type: azure-native:storage:StorageAccountStaticWebsite
        properties:
          accountName: ${sa.name}
          resourceGroupName: ${resourceGroup.name}
          indexDocument: index.html
    
    The static website resource leverages the storage account and resource group names defined previously in your program. Now use all of these cloud resources and a local `FileAsset` resource to upload `index.html` into your storage container by adding the following at the end of the file (after enabling the static website support):
    // Upload the file
    const indexHtml = new storage.Blob("index.html", {
        resourceGroupName: resourceGroup.name,
        accountName: storageAccount.name,
        containerName: staticWebsite.containerName,
        source: new pulumi.asset.FileAsset("index.html"),
        contentType: "text/html",
    });
    
    # Upload the file
    index_html = storage.Blob("index.html",
        resource_group_name=resource_group.name,
        account_name=account.name,
        container_name=static_website.container_name,
        source=pulumi.FileAsset("index.html"),
        content_type="text/html")
    
    // Upload the file
    _, err = storage.NewBlob(ctx, "index.html", &storage.BlobArgs{
        ResourceGroupName: resourceGroup.Name,
        AccountName:       account.Name,
        ContainerName:     staticWebsite.ContainerName,
        Source:            pulumi.NewFileAsset("index.html"),
        ContentType:       pulumi.String("text/html"),
    })
    if err != nil {
        return err
    }
    
    // Upload the file
    var indexHtml = new Blob("index.html", new BlobArgs
    {
        ResourceGroupName = resourceGroup.Name,
        AccountName = storageAccount.Name,
        ContainerName = staticWebsite.ContainerName,
        Source = new FileAsset("./index.html"),
        ContentType = "text/html",
    });
    
    // Upload the file
    var index_html = new Blob("index.html", BlobArgs.builder()
                        .resourceGroupName(resourceGroup.name())
                        .accountName(storageAccount.name())
                        .containerName(staticWebsite.containerName())
                        .source(new FileAsset("index.html"))
                        .contentType("text/html")
                        .build());
    
    resources:
      # ...
      # Upload the file
      index-html:
        type: azure-native:storage:Blob
        properties:
          resourceGroupName: ${resourceGroup.name}
          accountName: ${sa.name}
          containerName: ${staticWebsite.containerName}
          source:
            fn::fileAsset: ./index.html
          contentType: text/html
          blobName: index.html
          type: Block
    

    At the end of the program, export the bucket’s endpoint URL:

    // Web endpoint to the website
    export const staticEndpoint = storageAccount.primaryEndpoints.web;
    

    Finally, at the end of __main__.py, export the resulting storage container’s endpoint URL to stdout for easy access:

    # Web endpoint to the website
    pulumi.export("staticEndpoint", account.primary_endpoints.web)
    

    Finally, at the end of main.go, export the resulting storage container’s endpoint URL to stdout for easy access:

    // Web endpoint to the website
    ctx.Export("staticEndpoint", account.PrimaryEndpoints.Web())
    

    Finally, at the end of Program.cs, export the resulting storage container’s endpoint URL to stdout for easy access:

    // Web endpoint to the website
    return new Dictionary<string, object?>
    {
        ["primaryStorageKey"] = primaryStorageKey,
        ["staticEndpoint"] = storageAccount.PrimaryEndpoints.Apply(primaryEndpoints => primaryEndpoints.Web)
    };
    

    Finally, at the end of App.java, export the resulting storage container’s endpoint URL to stdout for easy access:

    ctx.export("staticEndpoint", storageAccount.primaryEndpoints()
            .applyValue(EndpointsResponse::web));
    

    Finally, at the end of Pulumi.yaml in the outputs, export the resulting storage container’s endpoint URL to stdout for easy access:

    outputs:
      # ...
      staticEndpoint: ${sa.primaryEndpoints.web}
    

    Deploy changes

    Deploy the changes by running pulumi up again.

    $ pulumi up
    

    Pulumi will run the preview step of the update, which computes the minimally disruptive change to achieve the desired state described by the program.

    Previewing update (dev):
    
         Type                                                   Name                 Plan
         pulumi:pulumi:Stack                                    quickstart-dev
     +   ├─ azure-native:storage:StorageAccountStaticWebsite    staticWebsite        create
     +   └─ azure-native:storage:Blob                           index.html           create
    
    Outputs:
      + staticEndpoint   : "https://sa8dd8af62.z22.web.core.windows.net/"
    
    Resources:
        + 2 to create
        3 unchanged
    
    Do you want to perform this update?
      yes
    > no
      details
    

    Choosing yes will proceed with the update by uploading the index.html file to a storage container in your account and enabling static website support on the container.

    Do you want to perform this update? yes
    Updating (dev):
    
         Type                                                   Name                Status
         pulumi:pulumi:Stack                                    quickstart-dev
     +   ├─ azure-native:storage:StorageAccountStaticWebsite    staticWebsite       created
     +   └─ azure-native:storage:Blob                           index.html          created
    
    Outputs:
        primaryStorageKey: "<key_value>"
      + staticEndpoint   : "https://sa8dd8af62.z22.web.core.windows.net/"
    
    Resources:
        + 2 created
        3 unchanged
    
    Duration: 4s
    

    Check out your new website at the URL or make a curl request:

    $ curl $(pulumi stack output staticEndpoint)
    
    $ curl $(pulumi stack output staticEndpoint)
    
    $ curl $(pulumi stack output staticEndpoint)
    
    $ curl $(pulumi stack output staticEndpoint)
    
    $ curl $(pulumi stack output staticEndpoint)
    
    $ curl $(pulumi stack output staticEndpoint)
    

    And you should see:

    <html>
        <body>
            <h1>Hello, Pulumi!</h1>
        </body>
    </html>
    

    Destroy stack

    Now that you’ve seen how to deploy changes to our program, let’s clean up and tear down the resources that are part of your stack.

    To destroy resources, run the following:

    $ pulumi destroy
    
    Previewing destroy (dev):
    
         Type                                                         Name                     Plan
     -   pulumi:pulumi:Stack                                          quickstart-dev           delete
     -   ├─ azure-native:storage:Blob                                 index.html               delete
     -   ├─ azure-native:storage:StorageAccountStaticWebsite          staticWebsite            delete
     -   ├─ azure-native:storage:StorageAccount                       sa                       delete
     -   └─ azure-native:resources:ResourceGroup                      resourceGroup            delete
    
    Outputs:
      - primaryStorageKey: "<key_value>"
      - staticEndpoint   : "https://sa8dd8af62.z22.web.core.windows.net/"
    
    Resources:
        - 5 to delete
    
    Do you want to perform this destroy? yes
    Destroying (dev):
    
         Type                                                   Name                Status
     -   pulumi:pulumi:Stack                                    quickstart-dev      deleted
     -   ├─ azure-native:storage:Blob                           index.html          deleted
     -   ├─ azure-native:storage:StorageAccountStaticWebsite    staticWebsite       deleted
     -   ├─ azure-native:storage:StorageAccount                 sa                  deleted
     -   └─ azure-native:resources:ResourceGroup                resourceGroup       deleted
    
    Outputs:
      - primaryStorageKey: "<key_value>"
      - staticEndpoint   : "https://sa8dd8af62.z22.web.core.windows.net/"
    
    Resources:
        - 5 deleted
    
    Duration: 53s
    

    To delete the stack itself, run pulumi stack rm. This removes the stack and the update history from Pulumi Cloud.

    Next steps

    Congrats! You’ve deployed your first project on Azure with Pulumi. Try a next step!

      Register for Pulumi UP, June 15, 2023