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!