In this post, we will use Terraform to
1. Create a static website and hosted on Azure Storage Account
2. Config CDN for this website using Azure Front Door.
Configure Terraform provider.
For provider authentication, we will use Service Principal with Client ID and Secret. To create an app registration, go to Microsoft Entra ID services, create an App Registration and grant Contributor role on the current azure subscription.

For provider use service principal authentication method, we wil enable storage_use_azuread in provider setting to disable Access Key authentication method on Storage Account (Will see later).
# Azure Provider source and version being used
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.84.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {
}
storage_use_azuread = true
}
After creating App Registration credetial, use Azure CLI to login and set azure subscription id.
az login --service-principal -u $CLIENT_ID -p $CLIENT_PASSWORD --tenant $TENANT_ID
az account set --subscription $SUBSCRIPTION_ID
Configure Terraform variables.
We only create a Resource Group, a Storage Account and Front Door so these variables is enough.
variable "resource_group_name" {
type = string
}
variable "fd_name" {
type = string
}
variable "web_name" {
type = string
}
variable "location" {
type = string
}
variable "tags" {
type = map(any)
}
I created ".auto.tfvars" to set value for those variables.
location = "southeastasia"
resource_group_name = "acc1"
fd_name = "acc1-fd"
web_name = "acc1fdweb"
tags = {}
Init Terraform Project
We will create main.tf
this file include all resource to be created. Run tf init
to initial project. The directory structure is like
Desktop/acc1/static-web
❯ tree
.
├── main.tf
├── outputs.tf
├── provider.tf
├── .auto.tfvars
├── terraform.tfstate
└── variables.tf
Create Resource Group
resource "azurerm_resource_group" "this" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
Create Storage Account
We create a storage account, enable static website setting and also disable shared_access_key_enabled
(Only allow use Azure AD to authenticate)
resource "azurerm_storage_account" "web" {
name = var.web_name
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
account_tier = "Standard"
account_replication_type = "GRS"
access_tier = "Hot"
shared_access_key_enabled = false
default_to_oauth_authentication = true
static_website {
index_document = "index.html"
error_404_document = "index.html"
}
infrastructure_encryption_enabled = true
routing {
publish_microsoft_endpoints = true
}
tags = var.tags
}
After running tf apply -auto-approve
we will see this resource in Azure Portal.

Note:
To be able to view/upload blog data you must grant a data access role, such as Storage Blob Data Reader or Storage Blob Data Contributor.
The website setting.

The "$web" container, where we will upload static file.

Create Front Door
We will create a Front Door Profile, an Endpojnt, configuration default route to forward request to Storage Website. For content caching, we only enable cache for specific file, css, js, image for example.
resource "azurerm_cdn_frontdoor_profile" "this" {
name = var.fd_name
resource_group_name = azurerm_resource_group.this.name
response_timeout_seconds = 16
sku_name = "Standard_AzureFrontDoor"
tags = var.tags
}
resource "azurerm_cdn_frontdoor_endpoint" "web" {
name = "web"
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.this.id
tags = var.tags
}
resource "azurerm_cdn_frontdoor_rule_set" "cache" {
name = "caching"
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.this.id
}
resource "azurerm_cdn_frontdoor_rule" "cache" {
depends_on = [
azurerm_cdn_frontdoor_origin_group.web,
azurerm_cdn_frontdoor_origin.web
]
name = "static"
cdn_frontdoor_rule_set_id = azurerm_cdn_frontdoor_rule_set.cache.id
order = 1
behavior_on_match = "Stop"
conditions {
url_file_extension_condition {
operator = "Equal"
match_values = ["css", "js", "ico", "png", "jpeg", "jpg", ".map"]
}
}
actions {
route_configuration_override_action {
compression_enabled = true
cache_behavior = "HonorOrigin"
}
}
}
resource "azurerm_cdn_frontdoor_origin_group" "web" {
name = "web"
cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.this.id
load_balancing {
additional_latency_in_milliseconds = 0
sample_size = 16
successful_samples_required = 3
}
}
resource "azurerm_cdn_frontdoor_origin" "web" {
depends_on = [azurerm_storage_account.web]
name = "web"
cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.web.id
enabled = true
certificate_name_check_enabled = false
host_name = azurerm_storage_account.web.primary_web_host
http_port = 80
https_port = 443
origin_host_header = azurerm_storage_account.web.primary_web_host
priority = 1
weight = 1
}
resource "azurerm_cdn_frontdoor_route" "default" {
name = "default"
cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.web.id
cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.web.id
cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.web.id]
cdn_frontdoor_rule_set_ids = [azurerm_cdn_frontdoor_rule_set.cache.id]
enabled = true
forwarding_protocol = "MatchRequest"
https_redirect_enabled = true
patterns_to_match = ["/*"]
supported_protocols = ["Http", "Https"]
link_to_default_domain = true
}
After running terraform apply
you will see the Front Door in Azure Portal

Uploading source code to Storage Account.
I will use https://create-react-app.dev/ to create a reactjs app, then build to static file. After building we have these files.
my-reactjs-app
❯ tree build
build
├── asset-manifest.json
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── static
├── css
│ ├── main.f855e6bc.css
│ └── main.f855e6bc.css.map
├── js
│ ├── 787.c5090bc2.chunk.js
│ ├── 787.c5090bc2.chunk.js.map
│ ├── main.4e817f22.js
│ ├── main.4e817f22.js.LICENSE.txt
│ └── main.4e817f22.js.map
└── media
└── logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg
To be able to sync the build file to Storage Account, i will use AZCopy. Download and installing this tool on your os.
Login with azcopy
Set the environment variable AZCOPY_SPA_CLIENT_SECRET to the client secret for secret based service principal auth.azcopy login --service-principal --tenant <your tenant id> --application-id <your service principal's application ID>
Sync build file
Run this script to sync build file to $web container on Storage Account.
azcopy sync build 'https://acc1fdweb.blob.core.windows.net/$web' --recursive=true

Go to Portal to verify

Copy the Front Door endpoint, this is our website address.

Our website is showed.
