One-Shot: Azure Static Websites with Custom Domains Using Terraform
Custom domains add professionalism and credibility to static websites, especially when hosted on Azure Storage Accounts. However, achieving this configuration with Terraform in a single terraform apply is currently not possible due to co-dependencies between DNS configuration and Azure’s domain verification process. This article walks you through the process of setting up a static website with a custom domain on Azure Storage Account using Terraform, explaining the challenges and offering a phased workaround.
Introduction to the Problem
When configuring a static website on Azure Storage Account with a custom domain using Terraform, you encounter a nested block (custom_domain) that requires pre-existing DNS records to pass verification. This dependency means that the domain configuration cannot be completed in a single Terraform run. If there were an independent Terraform resource for the custom_domain block, it would allow provisioning the storage account, setting up DNS records, and enabling the custom domain in a seamless workflow. Unfortunately, without this functionality, we must implement a two-phase approach. https://github.com/hashicorp/terraform-provider-azurerm/issues/16766
Provisioning the Storage Account and Static Website
To host a static website, we first provision an Azure Storage Account. Terraform allows two approaches to configure the static website feature: a nested block within the azurerm_storage_account resource or an independent azurerm_storage_account_static_website resource.
Nested Block Configuration
resource "azurerm_storage_account" "main" {
name = "st${random_string.main.result}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "RAGZRS"
https_traffic_only_enabled = false
static_website {
index_document = "index.html"
error_404_document = "404.html"
}
}
Independent Resource Configuration
resource "azurerm_storage_account_static_website" "main" {
storage_account_id = azurerm_storage_account.main.id
index_document = "index.html"
error_404_document = "404.html"
}
Both approaches provision the storage account with static website capabilities, but they do not yet address the custom domain setup.
Introducing Custom Domains with Dynamic Blocks
The custom_domain configuration requires a validated DNS record, which in turn needs the storage account endpoint. To manage this dependency, we use a dynamic block and two Terraform runs.
Dynamic Block for Custom Domain
dynamic "custom_domain" {
for_each = var.custom_domain != null ? [0] : []
content {
name = var.custom_domain
use_subdomain = false
}
}
This configuration allows us to skip setting the custom domain during the initial Terraform run and enable it later once the DNS records are validated.
Input Variables for Flexibility
We parameterize the domain name and sub-domain using input variables:
variable "domain_name" {
type = string
}
variable "domain_prefix" {
type = string
default = null
}
variable "storage_custom_domain_enabled" {
type = bool
}
This setup allows different environments (e.g., DEV, PROD) to use unique sub-domains.
Configuring DNS Records with Cloudflare
Azure requires DNS records to validate the custom domain. We use the Cloudflare provider to create these records. Before that, we clean up the storage account endpoint for record creation.
Cleaning Up the Storage Account Endpoint
locals {
remove_prefix_storage_endpoint = replace(azurerm_storage_account.main.primary_blob_endpoint, "https://", "")
storage_endpoint = replace(local.remove_prefix_storage_endpoint, "/", "")
}
Creating the asverify Record
resource "cloudflare_record" "asverify" {
zone_id = data.cloudflare_zone.main.id
name = "asverify"
comment = "Terraform: Azure Custom Domain Verify"
content = "asverify.${local.storage_endpoint}"
type = "CNAME"
proxied = false
}
Creating the Actual Custom Domain Record
After validation, we create the CNAME record pointing to the storage account endpoint.
Two-Phase Terraform Execution
First Terraform Apply:
- Provision the storage account and static website configuration.
- Create the DNS asverify record.
- Validate the custom domain in Azure.
Second Terraform Apply:
- Enable the custom_domain dynamic block by setting storage_custom_domain_enabled to true.
- Rerun Terraform to finalize the custom domain configuration.
Common Error and Resolution
If you attempt to enable the custom domain prematurely, you may encounter the following error:
│ Error: updating Storage Account (Subscription: “” 55│ Resource Group Name: “rg-foo-dotcom-prod” 56│ Storage Account Name: “st86cy2s5m”): performing Create: unexpected status 409 (409 Conflict) with error: StorageDomainNameCouldNotVerify: The custom domain name could not be verified. CNAME mapping from www.foo.io to any of st86cy2s5m.blob.core.windows.net,st86cy2s5m.z1.web.core.windows.net does not exist.
To resolve this, ensure both the asverify and custom domain DNS records are created before rerunning Terraform.
Conclusion
Setting up a static website with a custom domain on Azure Storage Account using Terraform requires careful handling of dependencies. While a single Terraform run is ideal, the current limitations necessitate a two-phase approach. By leveraging this technique — mashing up both dynamic blocks and well placed input variables, you can provision and validate custom domains and work around current technical constraints of the “azurerm” Terraform provider effectively.
With these steps, you can streamline your workflow until a more integrated solution becomes available in Terraform when we get a stand alone resource for the “custom_domain” nested block. Want to help get this feature faster? Go to the below GitHub Issue and smash the thumbs up on the issue!