Skip to main content

Search the Dev Center

Search tutorials, templates, examples, and more.

Azure Static Website

The Azure Static Website template scaffolds a Pulumi project that stores site files in an Azure Blob Storage account configured for static website hosting and serves them over HTTPS from the account’s website endpoint. The template ships with placeholder web content so the project deploys end to end out of the box. Requests go straight to the storage account. Adding a CDN shows how to put Azure Front Door in front of it, an optional step you can come back to later.

Architecture diagram of the Azure Static Website template Azure Blob Storage

Before you begin#

For this walkthrough, you'll need:

Create a new project#

Create a new project from the template:

Terminal window
mkdir my-site; cd my-site
pulumi new static-website-azure-typescript

The pulumi new command walks you through creating and configuring the new project and stack. A stack is an instance of a project, and you can have many of them (dev, staging, prod), each with its own configuration settings. Depending on the language, you may also be asked for your preferred package manager or toolchain.

This template’s settings include:

KeyDescriptionDefault
azure-native:locationThe Azure region to deploy intoWestUS
pathThe path to the folder containing the websitewww
indexDocumentThe file to use for top-level pagesindex.html
errorDocumentThe file to use for error pageserror.html
You can change values like these later with pulumi config set :
Terminal window
pulumi config set azure-native:location <value>
pulumi config set path <value>
pulumi config set indexDocument <value>
pulumi config set errorDocument <value>

Review the code#

When setup completes, you’ll see a few files:

  • Pulumi.yaml contains the project name and the language it’s written in.
  • Pulumi.dev.yaml contains the dev stack’s settings, including the values you just entered.
  • index.ts contains the program code that declares your cloud resources.

Take a moment to open them up and have a look. Here’s what’s in index.ts:

index.ts
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
import * as synced_folder from "@pulumi/synced-folder";
// Import the program's configuration settings.
const config = new pulumi.Config();
const path = config.get("path") || "www";
const indexDocument = config.get("indexDocument") || "index.html";
const errorDocument = config.get("errorDocument") || "error.html";
// Create a resource group for the website.
const resourceGroup = new azure_native.resources.ResourceGroup("resource-group", {});
// Create a blob storage account.
const account = new azure_native.storage.StorageAccount("account", {
resourceGroupName: resourceGroup.name,
kind: "StorageV2",
sku: {
name: "Standard_LRS",
},
});
// Configure the storage account as a website.
const website = new azure_native.storage.StorageAccountStaticWebsite("website", {
resourceGroupName: resourceGroup.name,
accountName: account.name,
indexDocument: indexDocument,
error404Document: errorDocument,
});
// Use a synced folder to manage the files of the website.
const syncedFolder = new synced_folder.AzureBlobFolder("synced-folder", {
path: path,
resourceGroupName: resourceGroup.name,
storageAccountName: account.name,
containerName: website.containerName,
});
// Pull the hostname out of the storage-account endpoint.
const originHostname = account.primaryEndpoints.apply(endpoints => new URL(endpoints.web)).hostname;
// Export the URL and hostname of the storage account's website.
export const originURL = account.primaryEndpoints.apply(endpoints => endpoints.web);
export { originHostname };

Deploy the project#

Deploy with pulumi up. It starts with a preview of what it’s about to create and asks before it changes anything. Choose yes to deploy.

Terminal window
pulumi up

Pulumi adds a random suffix to most resource names, so the resource group resource-group becomes something like resource-group8f3c2a1b. This is autonaming. It lets you deploy the same program to more than one stack, and lets Pulumi create a replacement before it deletes the old resource, without names colliding.

When it finishes, Pulumi prints the stack’s outputs, values it only knows once the resources exist:

originURL

The fully-qualified HTTPS URL of the storage account’s website endpoint.

originHostname

The provider-assigned hostname of the storage account’s website endpoint.

Read one any time with pulumi stack output <name>, or pass it to another command. This opens the site in your browser:

Terminal window
open $(pulumi stack output originURL)

To check it from the terminal, fetch the page with curl. It prints the template’s placeholder page, which says Hello, world!. The website endpoint can take a few seconds to start serving, so if curl prints a WebsiteDisabled error, wait a moment and try again:

Terminal window
curl -s $(pulumi stack output originURL)
Need help?

If pulumi up asks you to run az login, or says your sign-in has expired, sign in again:

Terminal window
az login

Still stuck? Let us know.

Customize the project#

Projects created with this template expose the following configuration settings:

path

The path to the folder containing the files of the website. Defaults to www, which is the name (and relative path) of the folder included with the template.

indexDocument

The file to use for top-level pages. Defaults to index.html.

errorDocument

The file to use for error pages. Defaults to error.html.

All of these settings are optional and may be adjusted either by editing the stack configuration file directly (by default, Pulumi.dev.yaml) or by changing their values with pulumi config set.

Using your own web content#

If you already have a static website you’d like to deploy on Azure with Pulumi, you can do so either by replacing placeholder content in the www folder or by configuring the stack to point to another folder on your computer with the path setting. Replace ../my-existing-website/build with the path to your site’s files:

Terminal window
pulumi config set path ../my-existing-website/build
pulumi up

Changing the error page#

The error page is what the site returns for a path it doesn’t have. Point it at the home page instead, then ask for a page that doesn’t exist. curl prints the home page, with Hello, world!, in place of the template’s Oops! That page wasn't found. If it still prints the old error page, wait a few seconds and run curl again:

Terminal window
pulumi config set errorDocument index.html
pulumi up
curl -s $(pulumi stack output originURL)no-such-page

To cache your site closer to your visitors, Adding a CDN shows how to put Azure Front Door in front of it. It’s optional, and it takes about 20 minutes to start serving, so it’s a good one to come back to once you’re comfortable with the project.

Next steps#

Templated projects are meant to be customized. Here’s one optional addition.

Adding a CDN#

Your site works as deployed; the storage account serves it over HTTPS. Optionally, you can also add a CDN to cache your files closer to your visitors.

On Azure that means Azure Front Door Standard, the replacement for the classic Azure CDN. Front Door takes a while to set up and tear down, so it’s something to come back to once you’re comfortable with the project. After pulumi up, the Front Door URL can return a 404 for up to about 25 minutes while its configuration propagates, and pulumi destroy takes about 15 to 20 minutes. Front Door Standard also has a monthly base fee, which the storage account’s website endpoint doesn’t. See Azure Front Door pricing for current rates.

Add these lines to the end of index.ts:

// Create an Azure Front Door profile and endpoint.
const profile = new azure_native.cdn.Profile("profile", {
resourceGroupName: resourceGroup.name,
location: "global",
sku: {
name: "Standard_AzureFrontDoor",
},
});
const endpoint = new azure_native.cdn.AFDEndpoint("endpoint", {
resourceGroupName: resourceGroup.name,
profileName: profile.name,
location: "global",
});
// Point Front Door at the storage account's website endpoint.
const originGroup = new azure_native.cdn.AFDOriginGroup("origin-group", {
resourceGroupName: resourceGroup.name,
profileName: profile.name,
loadBalancingSettings: {
sampleSize: 4,
successfulSamplesRequired: 3,
},
});
const origin = new azure_native.cdn.AFDOrigin("origin", {
resourceGroupName: resourceGroup.name,
profileName: profile.name,
originGroupName: originGroup.name,
hostName: originHostname,
originHostHeader: originHostname,
});
// Route every request on the endpoint to the origin, over HTTPS.
const route = new azure_native.cdn.Route("route", {
resourceGroupName: resourceGroup.name,
profileName: profile.name,
endpointName: endpoint.name,
originGroup: {
id: originGroup.id,
},
supportedProtocols: ["Http", "Https"],
patternsToMatch: ["/*"],
forwardingProtocol: "HttpsOnly",
linkToDefaultDomain: "Enabled",
httpsRedirect: "Enabled",
}, { dependsOn: [origin] });
// Export the URL and hostname of the Front Door endpoint.
export const cdnURL = pulumi.interpolate`https://${endpoint.hostName}`;
export const cdnHostname = endpoint.hostName;

Deploy the changes:

Terminal window
pulumi up

When the Front Door URL starts serving, curl prints the same Hello, world! page as the storage account. Until then it returns a 404, so wait a few minutes and try again:

Terminal window
curl -s $(pulumi stack output cdnURL)

Cleaning up#

You can cleanly destroy the stack and all of its infrastructure with pulumi destroy:

Terminal window
pulumi destroy

If you added Front Door, pulumi destroy takes 15 to 20 minutes.

Learn more#

Related

The infrastructure as code platform for any cloud.