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.
Before you begin#
For this walkthrough, you'll need:
- The Pulumi CLI
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
For this walkthrough, you'll need:
- The Pulumi CLI
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
For this walkthrough, you'll need:
- The Pulumi CLI
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
- A current version of Go installed locally
For this walkthrough, you'll need:
- The Pulumi CLI
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
- A current version of .NET installed locally
For this walkthrough, you'll need:
- The Pulumi CLI
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
For this walkthrough, you'll need:
- A current version of the Pulumi CLI (Pulumi HCL requires no additional runtime)
- The Azure CLI (optional, but recommended and used in this guide)
- Permission to manage Azure Storage
Create a new project#
Create a new project from the template:
mkdir my-site; cd my-sitepulumi new static-website-azure-typescriptmkdir my-site; cd my-sitepulumi new static-website-azure-typescriptmkdir my-site; cd my-sitepulumi new static-website-azure-pythonmkdir my-site; cd my-sitepulumi new static-website-azure-pythonmkdir my-site; cd my-sitepulumi new static-website-azure-gomkdir my-site; cd my-sitepulumi new static-website-azure-gomkdir my-site; cd my-sitepulumi new static-website-azure-csharpmkdir my-site; cd my-sitepulumi new static-website-azure-csharpmkdir my-site; cd my-sitepulumi new static-website-azure-yamlmkdir my-site; cd my-sitepulumi new static-website-azure-yamlmkdir my-site; cd my-sitepulumi new static-website-azure-hclmkdir my-site; cd my-sitepulumi new static-website-azure-hclThe 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:
| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS |
| path | The path to the folder containing the website | www |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS |
| path | The path to the folder containing the website | www |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS |
| path | The path to the folder containing the website | www |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS |
| path | The path to the folder containing the website | www |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS |
| path | The path to the folder containing the website | www |
| indexDocument | The file to use for top-level pages | index.html |
| errorDocument | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set indexDocument <value>pulumi config set errorDocument <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set indexDocument "<value>"pulumi config set errorDocument "<value>"| Key | Description | Default |
|---|---|---|
| azure-native:location | The Azure region to deploy into | WestUS2 |
| path | The path to the folder containing the website | www |
| index_document | The file to use for top-level pages | index.html |
| error_document | The file to use for error pages | error.html |
pulumi config set
:
pulumi config set azure-native:location <value>pulumi config set path <value>pulumi config set index_document <value>pulumi config set error_document <value>pulumi config set azure-native:location "<value>"pulumi config set path "<value>"pulumi config set index_document "<value>"pulumi config set error_document "<value>"Review the code#
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains the program code that declares your cloud resources.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the resources it declares.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.
When setup completes, you’ll see a few files:
Pulumi.yamlcontains the project name and the language it’s written in.Pulumi.dev.yamlcontains thedevstack’s settings, including the values you just entered.index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tfcontains 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.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yamlmain.tf:
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 };from urllib.parse import urlparseimport pulumiimport pulumi_azure_native as azure_nativeimport pulumi_synced_folder as synced_folder
# Import the program's configuration settings.config = pulumi.Config()path = config.get("path") or "www"index_document = config.get("indexDocument") or "index.html"error_document = config.get("errorDocument") or "error.html"
# Create a resource group for the website.resource_group = azure_native.resources.ResourceGroup("resource-group")
# Create a blob storage account.account = azure_native.storage.StorageAccount( "account", resource_group_name=resource_group.name, kind="StorageV2", sku={ "name": "Standard_LRS", },)
# Configure the storage account as a website.website = azure_native.storage.StorageAccountStaticWebsite( "website", resource_group_name=resource_group.name, account_name=account.name, index_document=index_document, error404_document=error_document,)
# Use a synced folder to manage the files of the website.synced_folder = synced_folder.AzureBlobFolder( "synced-folder", path=path, resource_group_name=resource_group.name, storage_account_name=account.name, container_name=website.container_name,)
# Pull the hostname out of the storage-account endpoint.origin_hostname = account.primary_endpoints.web.apply( lambda endpoint: urlparse(endpoint).hostname)
# Export the URL and hostname of the storage account's website.pulumi.export("originURL", account.primary_endpoints.web)pulumi.export("originHostname", origin_hostname)package main
import ( "net/url"
resources "github.com/pulumi/pulumi-azure-native-sdk/resources/v3" storage "github.com/pulumi/pulumi-azure-native-sdk/storage/v3" synced "github.com/pulumi/pulumi-synced-folder/sdk/go/synced-folder" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config")
func main() { pulumi.Run(func(ctx *pulumi.Context) error {
// Import the program's configuration settings. cfg := config.New(ctx, "") path := "www" if param := cfg.Get("path"); param != "" { path = param } indexDocument := "index.html" if param := cfg.Get("indexDocument"); param != "" { indexDocument = param } errorDocument := "error.html" if param := cfg.Get("errorDocument"); param != "" { errorDocument = param }
// Create a resource group for the website. resourceGroup, err := resources.NewResourceGroup(ctx, "resource-group", nil) if err != nil { return err }
// Create a blob storage account. account, err := storage.NewStorageAccount(ctx, "account", &storage.StorageAccountArgs{ ResourceGroupName: resourceGroup.Name, Kind: pulumi.String("StorageV2"), Sku: &storage.SkuArgs{ Name: pulumi.String("Standard_LRS"), }, }) if err != nil { return err }
// Configure the storage account as a website. website, err := storage.NewStorageAccountStaticWebsite(ctx, "website", &storage.StorageAccountStaticWebsiteArgs{ ResourceGroupName: resourceGroup.Name, AccountName: account.Name, IndexDocument: pulumi.String(indexDocument), Error404Document: pulumi.String(errorDocument), }) if err != nil { return err }
// Use a synced folder to manage the files of the website. _, err = synced.NewAzureBlobFolder(ctx, "synced-folder", &synced.AzureBlobFolderArgs{ Path: pulumi.String(path), ResourceGroupName: resourceGroup.Name, StorageAccountName: account.Name, ContainerName: website.ContainerName, }) if err != nil { return err }
// Pull the hostname out of the storage-account endpoint. originHostname := account.PrimaryEndpoints.ApplyT(func(endpoints storage.EndpointsResponse) (string, error) { parsed, err := url.Parse(endpoints.Web) if err != nil { return "", err } return parsed.Hostname(), nil }).(pulumi.StringOutput)
// Export the URL and hostname of the storage account's website. ctx.Export("originURL", account.PrimaryEndpoints.ApplyT(func(endpoints storage.EndpointsResponse) (string, error) { return endpoints.Web, nil }).(pulumi.StringOutput)) ctx.Export("originHostname", originHostname)
return nil })}using System;using System.Collections.Generic;using Pulumi;using AzureNative = Pulumi.AzureNative;using SyncedFolder = Pulumi.SyncedFolder;
return await Deployment.RunAsync(() =>{ // Import the program's configuration settings. var config = new Config(); var path = config.Get("path") ?? "www"; var indexDocument = config.Get("indexDocument") ?? "index.html"; var errorDocument = config.Get("errorDocument") ?? "error.html";
// Create a resource group for the website. var resourceGroup = new AzureNative.Resources.ResourceGroup("resource-group");
// Create a blob storage account. var account = new AzureNative.Storage.StorageAccount("account", new() { ResourceGroupName = resourceGroup.Name, Kind = "StorageV2", Sku = new AzureNative.Storage.Inputs.SkuArgs { Name = "Standard_LRS", }, });
// Configure the storage account as a website. var website = new AzureNative.Storage.StorageAccountStaticWebsite("website", new() { ResourceGroupName = resourceGroup.Name, AccountName = account.Name, IndexDocument = indexDocument, Error404Document = errorDocument, });
// Use a synced folder to manage the files of the website. var syncedFolder = new SyncedFolder.AzureBlobFolder("synced-folder", new() { Path = path, ResourceGroupName = resourceGroup.Name, StorageAccountName = account.Name, ContainerName = website.ContainerName, });
// Pull the hostname out of the storage-account endpoint. var originHostname = account.PrimaryEndpoints.Apply(endpoints => new Uri(endpoints.Web).Host);
// Export the URL and hostname of the storage account's website. return new Dictionary<string, object?> { ["originURL"] = account.PrimaryEndpoints.Apply(primaryEndpoints => primaryEndpoints.Web), ["originHostname"] = originHostname, };});name: my-projectdescription: A Pulumi YAML program to deploy a static website on Azureruntime: yaml
# Import the program's configuration settings.config: path: type: string default: www indexDocument: type: string default: index.html errorDocument: type: string default: error.html
# Pull the hostname out of the storage-account endpoint.variables: originHostname: fn::select: - 2 - fn::split: - / - ${account.primaryEndpoints.web}
resources:
# Create a resource group for the website. resource-group: type: azure-native:resources:ResourceGroup
# Create a blob storage account. account: type: azure-native:storage:StorageAccount properties: resourceGroupName: ${resource-group.name} kind: StorageV2 sku: name: Standard_LRS
# Configure the storage account as a website. website: type: azure-native:storage:StorageAccountStaticWebsite properties: resourceGroupName: ${resource-group.name} accountName: ${account.name} indexDocument: ${indexDocument} error404Document: ${errorDocument}
# Use a synced folder to manage the files of the website. synced-folder: type: synced-folder:index:AzureBlobFolder properties: path: ${path} resourceGroupName: ${resource-group.name} storageAccountName: ${account.name} containerName: ${website.containerName}
# Export the URL and hostname of the storage account's website.outputs: originURL: ${account.primaryEndpoints.web} originHostname: ${originHostname}terraform { required_providers { azure-native = { source = "pulumi/azure-native" } synced-folder = { source = "pulumi/synced-folder" } }}
variable "path" { description = "The path to the folder containing the website" type = string default = "www"}
variable "index_document" { description = "The file to use for top-level pages" type = string default = "index.html"}
variable "error_document" { description = "The file to use for error pages" type = string default = "error.html"}
# Create a resource group for the website.resource "azure-native_resources_resource_group" "resource-group" {}
# Create a blob storage account.resource "azure-native_storage_storage_account" "account" { resource_group_name = azure-native_resources_resource_group.resource-group.name kind = "StorageV2" sku = { name = "Standard_LRS" }}
# Configure the storage account as a website.resource "azure-native_storage_storage_account_static_website" "website" { resource_group_name = azure-native_resources_resource_group.resource-group.name account_name = azure-native_storage_storage_account.account.name index_document = var.index_document error404_document = var.error_document}
# Use a synced folder to manage the files of the website.resource "synced-folder_azure_blob_folder" "synced-folder" { path = var.path resource_group_name = azure-native_resources_resource_group.resource-group.name storage_account_name = azure-native_storage_storage_account.account.name container_name = azure-native_storage_storage_account_static_website.website.container_name}
# Export the URL and hostname of the storage account's website.output "origin_url" { value = azure-native_storage_storage_account.account.primary_endpoints.web}
output "origin_hostname" { value = split("/", azure-native_storage_storage_account.account.primary_endpoints.web)[2]}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.
pulumi uppulumi upPulumi 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.
- 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.
- 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.
- 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.
- 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.
- origin_url
The fully-qualified HTTPS URL of the storage account’s website endpoint.
- origin_hostname
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:
open $(pulumi stack output originURL)xdg-open $(pulumi stack output originURL)Start-Process $(pulumi stack output originURL)open $(pulumi stack output originURL)xdg-open $(pulumi stack output originURL)Start-Process $(pulumi stack output originURL)open $(pulumi stack output originURL)xdg-open $(pulumi stack output originURL)Start-Process $(pulumi stack output originURL)open $(pulumi stack output originURL)xdg-open $(pulumi stack output originURL)Start-Process $(pulumi stack output originURL)open $(pulumi stack output originURL)xdg-open $(pulumi stack output originURL)Start-Process $(pulumi stack output originURL)open $(pulumi stack output origin_url)xdg-open $(pulumi stack output origin_url)Start-Process $(pulumi stack output origin_url)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:
curl -s $(pulumi stack output originURL)curl.exe -s $(pulumi stack output originURL)curl -s $(pulumi stack output originURL)curl.exe -s $(pulumi stack output originURL)curl -s $(pulumi stack output originURL)curl.exe -s $(pulumi stack output originURL)curl -s $(pulumi stack output originURL)curl.exe -s $(pulumi stack output originURL)curl -s $(pulumi stack output originURL)curl.exe -s $(pulumi stack output originURL)curl -s $(pulumi stack output origin_url)curl.exe -s $(pulumi stack output origin_url)Need help?
If pulumi up asks you to run az login, or says your sign-in has expired, sign in again:
az loginaz loginStill 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.
- 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.
- 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.
- 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.
- 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.
- 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.- index_document
The file to use for top-level pages. Defaults to
index.html.- error_document
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:
pulumi config set path ../my-existing-website/buildpulumi uppulumi config set path ../my-existing-website/buildpulumi upChanging 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:
pulumi config set errorDocument index.htmlpulumi upcurl -s $(pulumi stack output originURL)no-such-pagepulumi config set errorDocument index.htmlpulumi upcurl.exe -s "$(pulumi stack output originURL)no-such-page"pulumi config set errorDocument index.htmlpulumi upcurl -s $(pulumi stack output originURL)no-such-pagepulumi config set errorDocument index.htmlpulumi upcurl.exe -s "$(pulumi stack output originURL)no-such-page"pulumi config set errorDocument index.htmlpulumi upcurl -s $(pulumi stack output originURL)no-such-pagepulumi config set errorDocument index.htmlpulumi upcurl.exe -s "$(pulumi stack output originURL)no-such-page"pulumi config set errorDocument index.htmlpulumi upcurl -s $(pulumi stack output originURL)no-such-pagepulumi config set errorDocument index.htmlpulumi upcurl.exe -s "$(pulumi stack output originURL)no-such-page"pulumi config set errorDocument index.htmlpulumi upcurl -s $(pulumi stack output originURL)no-such-pagepulumi config set errorDocument index.htmlpulumi upcurl.exe -s "$(pulumi stack output originURL)no-such-page"pulumi config set error_document index.htmlpulumi upcurl -s $(pulumi stack output origin_url)no-such-pagepulumi config set error_document index.htmlpulumi upcurl.exe -s "$(pulumi stack output origin_url)no-such-page"To see what else you can add, browse the Azure Native provider in the Pulumi Registry.
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;Add these lines to the end of __main__.py:
# Create an Azure Front Door profile and endpoint.profile = azure_native.cdn.Profile( "profile", resource_group_name=resource_group.name, location="global", sku={ "name": "Standard_AzureFrontDoor", },)
endpoint = azure_native.cdn.AFDEndpoint( "endpoint", resource_group_name=resource_group.name, profile_name=profile.name, location="global",)
# Point Front Door at the storage account's website endpoint.origin_group = azure_native.cdn.AFDOriginGroup( "origin-group", resource_group_name=resource_group.name, profile_name=profile.name, load_balancing_settings={ "sample_size": 4, "successful_samples_required": 3, },)
origin = azure_native.cdn.AFDOrigin( "origin", resource_group_name=resource_group.name, profile_name=profile.name, origin_group_name=origin_group.name, host_name=origin_hostname, origin_host_header=origin_hostname,)
# Route every request on the endpoint to the origin, over HTTPS.route = azure_native.cdn.Route( "route", resource_group_name=resource_group.name, profile_name=profile.name, endpoint_name=endpoint.name, origin_group={ "id": origin_group.id, }, supported_protocols=["Http", "Https"], patterns_to_match=["/*"], forwarding_protocol="HttpsOnly", link_to_default_domain="Enabled", https_redirect="Enabled", opts=pulumi.ResourceOptions(depends_on=[origin]),)
# Export the URL and hostname of the Front Door endpoint.pulumi.export("cdnURL", pulumi.Output.concat("https://", endpoint.host_name))pulumi.export("cdnHostname", endpoint.host_name)Add the Azure Native CDN module to the project:
go get github.com/pulumi/pulumi-azure-native-sdk/cdn/v3go get github.com/pulumi/pulumi-azure-native-sdk/cdn/v3Then import it in main.go:
import ( // ... cdn "github.com/pulumi/pulumi-azure-native-sdk/cdn/v3")And add these lines just above the final return nil:
// Create an Azure Front Door profile and endpoint.profile, err := cdn.NewProfile(ctx, "profile", &cdn.ProfileArgs{ ResourceGroupName: resourceGroup.Name, Location: pulumi.String("global"), Sku: &cdn.SkuArgs{ Name: pulumi.String("Standard_AzureFrontDoor"), },})if err != nil { return err}
endpoint, err := cdn.NewAFDEndpoint(ctx, "endpoint", &cdn.AFDEndpointArgs{ ResourceGroupName: resourceGroup.Name, ProfileName: profile.Name, Location: pulumi.String("global"),})if err != nil { return err}
// Point Front Door at the storage account's website endpoint.originGroup, err := cdn.NewAFDOriginGroup(ctx, "origin-group", &cdn.AFDOriginGroupArgs{ ResourceGroupName: resourceGroup.Name, ProfileName: profile.Name, LoadBalancingSettings: &cdn.LoadBalancingSettingsParametersArgs{ SampleSize: pulumi.Int(4), SuccessfulSamplesRequired: pulumi.Int(3), },})if err != nil { return err}
origin, err := cdn.NewAFDOrigin(ctx, "origin", &cdn.AFDOriginArgs{ ResourceGroupName: resourceGroup.Name, ProfileName: profile.Name, OriginGroupName: originGroup.Name, HostName: originHostname, OriginHostHeader: originHostname,})if err != nil { return err}
// Route every request on the endpoint to the origin, over HTTPS._, err = cdn.NewRoute(ctx, "route", &cdn.RouteArgs{ ResourceGroupName: resourceGroup.Name, ProfileName: profile.Name, EndpointName: endpoint.Name, OriginGroup: &cdn.ResourceReferenceArgs{ Id: originGroup.ID(), }, SupportedProtocols: pulumi.StringArray{pulumi.String("Http"), pulumi.String("Https")}, PatternsToMatch: pulumi.StringArray{pulumi.String("/*")}, ForwardingProtocol: pulumi.String("HttpsOnly"), LinkToDefaultDomain: pulumi.String("Enabled"), HttpsRedirect: pulumi.String("Enabled"),}, pulumi.DependsOn([]pulumi.Resource{origin}))if err != nil { return err}
// Export the URL and hostname of the Front Door endpoint.ctx.Export("cdnURL", pulumi.Sprintf("https://%s", endpoint.HostName))ctx.Export("cdnHostname", endpoint.HostName)Add these lines to Program.cs, just above the return new Dictionary statement:
// Create an Azure Front Door profile and endpoint.var profile = new AzureNative.Cdn.Profile("profile", new(){ ResourceGroupName = resourceGroup.Name, Location = "global", Sku = new AzureNative.Cdn.Inputs.SkuArgs { Name = "Standard_AzureFrontDoor", },});
var endpoint = new AzureNative.Cdn.AFDEndpoint("endpoint", new(){ ResourceGroupName = resourceGroup.Name, ProfileName = profile.Name, Location = "global",});
// Point Front Door at the storage account's website endpoint.var originGroup = new AzureNative.Cdn.AFDOriginGroup("origin-group", new(){ ResourceGroupName = resourceGroup.Name, ProfileName = profile.Name, LoadBalancingSettings = new AzureNative.Cdn.Inputs.LoadBalancingSettingsParametersArgs { SampleSize = 4, SuccessfulSamplesRequired = 3, },});
var origin = new AzureNative.Cdn.AFDOrigin("origin", new(){ ResourceGroupName = resourceGroup.Name, ProfileName = profile.Name, OriginGroupName = originGroup.Name, HostName = originHostname, OriginHostHeader = originHostname,});
// Route every request on the endpoint to the origin, over HTTPS.var route = new AzureNative.Cdn.Route("route", new(){ ResourceGroupName = resourceGroup.Name, ProfileName = profile.Name, EndpointName = endpoint.Name, OriginGroup = new AzureNative.Cdn.Inputs.ResourceReferenceArgs { Id = originGroup.Id, }, SupportedProtocols = { "Http", "Https" }, PatternsToMatch = { "/*" }, ForwardingProtocol = "HttpsOnly", LinkToDefaultDomain = "Enabled", HttpsRedirect = "Enabled",}, new CustomResourceOptions { DependsOn = { origin } });Then add the URL and hostname of the Front Door endpoint to the outputs:
return new Dictionary<string, object?>{ // ... ["cdnURL"] = endpoint.HostName.Apply(hostName => $"https://{hostName}"), ["cdnHostname"] = endpoint.HostName,};Add these resources to the end of the resources section of Pulumi.yaml:
resources: # ...
# Create an Azure Front Door profile and endpoint. profile: type: azure-native:cdn:Profile properties: resourceGroupName: ${resource-group.name} location: global sku: name: Standard_AzureFrontDoor
endpoint: type: azure-native:cdn:AFDEndpoint properties: resourceGroupName: ${resource-group.name} profileName: ${profile.name} location: global
# Point Front Door at the storage account's website endpoint. origin-group: type: azure-native:cdn:AFDOriginGroup properties: resourceGroupName: ${resource-group.name} profileName: ${profile.name} loadBalancingSettings: sampleSize: 4 successfulSamplesRequired: 3
origin: type: azure-native:cdn:AFDOrigin properties: resourceGroupName: ${resource-group.name} profileName: ${profile.name} originGroupName: ${origin-group.name} hostName: ${originHostname} originHostHeader: ${originHostname}
# Route every request on the endpoint to the origin, over HTTPS. route: type: azure-native:cdn:Route properties: resourceGroupName: ${resource-group.name} profileName: ${profile.name} endpointName: ${endpoint.name} originGroup: id: ${origin-group.id} supportedProtocols: - Http - Https patternsToMatch: - /* forwardingProtocol: HttpsOnly linkToDefaultDomain: Enabled httpsRedirect: Enabled options: dependsOn: - ${origin}Then add the URL and hostname of the Front Door endpoint to the outputs section:
outputs: # ... cdnURL: https://${endpoint.hostName} cdnHostname: ${endpoint.hostName}Add these lines to the end of main.tf:
locals { origin_hostname = split("/", azure-native_storage_storage_account.account.primary_endpoints.web)[2]}
# Create an Azure Front Door profile and endpoint.resource "azure-native_cdn_profile" "profile" { resource_group_name = azure-native_resources_resource_group.resource-group.name location = "global" sku = { name = "Standard_AzureFrontDoor" }}
resource "azure-native_cdn_a_f_d_endpoint" "endpoint" { resource_group_name = azure-native_resources_resource_group.resource-group.name profile_name = azure-native_cdn_profile.profile.name location = "global"}
# Point Front Door at the storage account's website endpoint.resource "azure-native_cdn_a_f_d_origin_group" "origin-group" { resource_group_name = azure-native_resources_resource_group.resource-group.name profile_name = azure-native_cdn_profile.profile.name load_balancing_settings = { sample_size = 4 successful_samples_required = 3 }}
resource "azure-native_cdn_a_f_d_origin" "origin" { resource_group_name = azure-native_resources_resource_group.resource-group.name profile_name = azure-native_cdn_profile.profile.name origin_group_name = azure-native_cdn_a_f_d_origin_group.origin-group.name host_name = local.origin_hostname origin_host_header = local.origin_hostname}
# Route every request on the endpoint to the origin, over HTTPS.resource "azure-native_cdn_route" "route" { resource_group_name = azure-native_resources_resource_group.resource-group.name profile_name = azure-native_cdn_profile.profile.name endpoint_name = azure-native_cdn_a_f_d_endpoint.endpoint.name origin_group = { id = azure-native_cdn_a_f_d_origin_group.origin-group.id } supported_protocols = ["Http", "Https"] patterns_to_match = ["/*"] forwarding_protocol = "HttpsOnly" link_to_default_domain = "Enabled" https_redirect = "Enabled"
depends_on = [azure-native_cdn_a_f_d_origin.origin]}
# Export the URL and hostname of the Front Door endpoint.output "cdn_url" { value = "https://${azure-native_cdn_a_f_d_endpoint.endpoint.host_name}"}
output "cdn_hostname" { value = azure-native_cdn_a_f_d_endpoint.endpoint.host_name}Deploy the changes:
pulumi uppulumi upWhen 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:
curl -s $(pulumi stack output cdnURL)curl.exe -s $(pulumi stack output cdnURL)curl -s $(pulumi stack output cdnURL)curl.exe -s $(pulumi stack output cdnURL)curl -s $(pulumi stack output cdnURL)curl.exe -s $(pulumi stack output cdnURL)curl -s $(pulumi stack output cdnURL)curl.exe -s $(pulumi stack output cdnURL)curl -s $(pulumi stack output cdnURL)curl.exe -s $(pulumi stack output cdnURL)curl -s $(pulumi stack output cdn_url)curl.exe -s $(pulumi stack output cdn_url)Cleaning up#
You can cleanly destroy the stack and all of its infrastructure with pulumi destroy:
pulumi destroypulumi destroyIf you added Front Door, pulumi destroy takes 15 to 20 minutes.
Learn more#
- Browse other architecture templates in the Templates gallery.
- Explore the Azure Native provider API docs in the Pulumi Registry.
- Walk through Pulumi from the ground up in Pulumi Tutorials.
- Read the latest Azure posts on the Pulumi blog.