Skip to content

Click on each book below to review & buy on Amazon.

As an Amazon Associate, I earn from qualifying purchases.


Terraform - Azure Static Web App - Custom Domain - Azure DevOps Pipelines

Guide to deploying Azure Static Web Apps with a custom domain using Terraform.

Azure DevOps Pipelines will also be created using Terraform.

Terraform Version1.4.5
Azurerm Version3.54.0
Azuredevops Version0.5.0

Prerequisites

The prerequisites are:

  • Your website needs to be held within an Azure DevOps repository.

  • Azure DevOps Personal Access Token with Permissions:

    • Code: Read, Write & Manage
    • Variable Groups: Read, Create, & Manage
    • Build: Read & execute
    • Project and Team: Read
  • Azure DevOps parallel jobs are required. Request Parallel Jobs (Free Tier).

  • Ability to add CNAME records for your domain.

  • Have authenticated to Azure, for example, by running az login.

  • Have authenticated to Azure DevOps, for example, by running az devops login --organization https://dev.azure.com/<org name>

Terraform Block

The Terrafrom block requires two arguments:

  • required_version: Set to the desired Terraform version.
  • required_providers: A required providers block. (See below for details).

For help on declaring versions, see Version Constraints

required_providers

There are two required providers:

  • azurerm: Manages Azure resources.
  • azuredevops: Manages Azure DevOps resources.

azurerm

This provider requires two arguments:

  • source: Set this to "azurerm".
  • version: Set to the desired provider version.

azuredevops

This provider requires two arguments:

  • source: Set this to "microsoft/azuredevops".
  • version: Set to the desired provider version.

Example Code

terraform {
  required_version = "=1.4.5"

  required_providers {
    azurerm = {
      source  = "azurerm"
      version = "=3.54"
    }

    azuredevops = {
      source  = "microsoft/azuredevops"
      version = "=0.5.0"
    }
  }
}

Providers

The azurerm & azuredevops providers require configuration.

azurerm Provider

There is are two arguments that may be useful to configure:

  • subscription_id: Declaring the Azure subscription_id is useful to save switching subscriptions manually at the command line.
  • prevent_deletion_if_contains_resources: This will be under the resource_group features as shown below. It ensures Terraform will not delete the resource group if any resources are present within it. true or false.

azuredevops Provider

To ensure the Personal Access Token (PAT) is not stored in code, a variable can be set which will prompt for the token at runtime:

variable "ado_personal_access_token" {
  sensitive = true
}

There are two required arguments:

  • org_service_url: The DevOps organisation. https://dev.azure.com/<org name>.
  • personal_access_token: Reference the PAT variable. sensitive(var.ado_personal_access_token).

Example Code

provider "azurerm" {
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

  features {
    resource_group {
      prevent_deletion_if_contains_resources = true
    }
  }
}

variable "ado_personal_access_token" {
  sensitive = true
}

provider "azuredevops" {
  org_service_url       = "https://dev.azure.com/demo-org"
  personal_access_token = sensitive(var.ado_personal_access_token)
}

Azure Resource Group

The azurerm_resource_group resource is used to manage Azure resource groups.

Required Arguments

The required arguments are:

  • location: The location for the resource groups meta data.
  • name: The name of the resource group. rg-<app or service name>-<subscription purpose>-<###>

Run az account list-locations -o table to get a list of locations.

Optional Arguments

The optional arguments are:

  • tags: The tags to be assigned to the resource.

Example Code

resource "azurerm_resource_group" "rg-stapp-001" {
  name     = "rg-stapp-demo-001"
  location = "ukwest"

  tags = {
    environment = "demo"
    source      = "terraform"
  }
}

Azurerm Static Site

The azurerm_static_site resource is used to manage Azure Static Web Apps.

Required Arguments

The required arguments are:

  • name: The name for the Azure Static Site.
  • location: The region for Azure Functions API/Staging Environments. Valid options are:
    • Central US
    • East US 2
    • East Asia
    • West Europe
    • West US 2
  • resource_group_name: Reference the name of the azurerm_resource_group resource declared earlier. azurerm_resource_group.rg-stapp-001.name

Optional Arguments

The optional arguments are:

  • sku_tier: Specifies the SKU tier. Free or Standard. Default is Free.
  • sku_size: Specifies the SKU size. Free or Standard. Default is Free.
  • tags: The tags to be assigned to the resource.

Example Code

resource "azurerm_static_site" "stapp-dtvlinux-001" {
  name                = "stapp-dtvlinux-demo-001"
  location            = "West Europe"
  resource_group_name = azurerm_resource_group.rg-stapp-001.name
  sku_tier            = "Free"
  sku_size            = "Free"

  tags                = {
    environment = "demo"
    source      = "terraform"
  }
}

Azure Static Site Custom Domain

The azurerm_static_site_custom_domain resource is used to manage custom domains for your site. i.e. www.example.com instead of random-name.azurestaticapps.net.

Required Arguments

The required arguments are:

  • static_site_id: Reference the ID of the azurerm_static_site resource declared earlier. azurerm_static_site.stapp-dtvlinux-001.id
  • domain_name: The custom domain name for the Azure Static Site. www.example.com
  • validation_type: Whether to validate your domain via CNAME or TXT records. cname-delegation or dns-txt-token. Use cname-delegation.

Example Code

resource "azurerm_static_site_custom_domain" "stapp-dtvlinux-001" {
  static_site_id  = azurerm_static_site.stapp-dtvlinux-001.id
  domain_name     = "www.example.com"
  validation_type = "cname-delegation"
}

Azure DevOps Variable Group

The azuredevops_variable_group resource manages variable groups within the Azure DevOps Pipeline Library.

Required Arguments

The required arguments are:

  • project_id: The ID of your Azure DevOps Project.
  • name: The name of the variable group. Azure uses the naming convention azure-static-web-apps-<webapp hostname>-variable-group.
  • allow_access: Whether all pipelines in the project can use the variable group. true or false. Use true.
  • variable: This block configures the variable options to use.
    • name: The variable name.
    • secret_value: The variables value. azurerm_static_site.stapp-dtvlinux-001.api_key.
    • is_secret: Whether the variable is a secret. true or false. Use true.

To find your project ID, run az repos list --query "[].{ProjectName:project.name, ProjectID:project.id}" --output table

Optional Argument

The optional argument is:

  • description: Give a description for your variable group.

Example Code

resource "azuredevops_variable_group" "var-grp-dtvlinux-001" {
  project_id   = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  name         = "azure-static-web-apps-dtvlinux-variable-group"
  allow_access = true
  description  = "Static Web App Variable Group"

  variable {
    name         = "AZURE_STATIC_WEB_APPS_API_TOKEN_DTVLINUX"
    secret_value = azurerm_static_site.stapp-dtvlinux-001.api_key
    is_secret    = true
  }
}

Azure DevOps Repository File

The azuredevops_git_repository_file resource is used to manage the azure-pipelines.yml file.

Required Arguments

The required arguments are:

  • repository_id: The ID of your Azure DevOps repository.
  • file: The file to manage. azure-pipelines.yml.
  • content: The contents of the azure-pipelines.yml file.
  • branch: Specify the branch the file should exist on. refs/head/master is the default.
  • overwrite_on_create: Enables overwritting if file already exists. true or false. Use true.

To find your repository ID, run az repos list --query "[].{RepoName:name, RepoID:id}" --output table

Example Code

resource "azuredevops_git_repository_file" "repo-file-dtvlinux-001" {
  repository_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  file                = "azure-pipelines.yml"
  content             = <<-EOT
    trigger:
      - refs/heads/main

    pool:
      vmImage: ubuntu-latest

    variables:
      - group: ${azuredevops_variable_group.var-grp-dtvlinux-001.name}

    steps:
      - checkout: self
        submodules: true
      - task: AzureStaticWebApp@0
        inputs:
          app_location: 'site'
          skip_app_build: true
          skip_api_build: true
          verbose: true
          azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_DTVLINUX)
  EOT
  branch              = "refs/heads/main"
  overwrite_on_create = true
}

Azure DevOps Build Definition

The azuredevops_build_definition resource is used to manage the build process for the Web App.

Required Arguments

The required arguments are:

  • project_id: The ID of your Azure DevOps Project.
  • ci_trigger: This block manages continuous integration options.
    • use_yaml: Whether to use the Azure pipelines file. true or false. Use true.
  • repository: This block configures the repository options to use.
    • repo_type: The repository type. GitHub, TfsGit, Bitbucket or GitHub Enterprise. Use TfsGit.
    • repo_id: The ID of your Azure DevOps repository.
    • branch_name: Specify the branch the file should exist on. master is the default.
    • yml_path: Set this to azure-pipelines.yml
  • variable_groups: This is a list of any variable groups to link to the build definition. [azuredevops_variable_group.var-grp-dtvlinux-001.id].

Example Code

resource "azuredevops_build_definition" "build-def-dtvlinux-001" {
  project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  name       = "Static Web App Build Definition"

  ci_trigger {
    use_yaml = true
  }

  repository {
    repo_type   = "TfsGit"
    repo_id     = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
    branch_name = "refs/heads/main"
    yml_path    = "azure-pipelines.yml"
  }

  variable_groups = [
    azuredevops_variable_group.var-grp-dtvlinux-001.id
  ]
}

Static Site Hostname Output

The Static Web Apps hostname is a required output. Set as below:

output "static_site_default_hostname" {
  value = "${azurerm_static_site.stapp-dtvlinux-001.default_host_name}."
}

Terraform - 1st Run

It is now time to run Terraform commands.

Enter your Azure DevOps PAT token when prompted.

Terraform Initialization

To initialise the Terraform and provider versions, run terraform init.

Terraform Apply

Run terraform apply and review the changes/new resources Terraform will implement.

Answer yes if you wish for Terraform to apply those changes, or no to cancel the run.

On the first run you will receive Error: creating Static Site Custom Domain. A later step in this guide adds a CNAME record to your domain before we run terraform apply again.

Custom Domain

You will need to add a CNAME record to your domain.

Azure Static Web App Default Hostname

Get the Static Web App default hostname by running:

terraform output static_site_default_hostname

Create CNAME Record

Follow your domain registrars instructions on how to add a CNAME record.

An example record would be the below, where the data field contains the output from the previous step:

Make sure to exclude the quotes and include the final dot (.)

Type Name Data
CNAME www black-sea-xxxxxxxxx.x.azurestaticapps.net.

Terraform - 2nd Run

Now the CNAME record has been added, Terraform can run again to add the custom domain to the Web App.

Terraform Apply

Run terraform apply and review the changes/new resources Terraform will implement.

Answer yes if you wish for Terraform to apply those changes, or no to cancel the run.

Visit The Web App via Custom Domain

You can now visit you Web App using your custom domain.

Type it into a browser to give it a try!

Full Example Code

terraform {
  required_version = "=1.4.5"

  required_providers {
    azurerm = {
      source  = "azurerm"
      version = "=3.54"
    }

    azuredevops = {
      source  = "microsoft/azuredevops"
      version = "=0.5.0"
    }
  }
}

provider "azurerm" {
  subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

  features {
    resource_group {
      prevent_deletion_if_contains_resources = true
    }
  }
}

variable "ado_personal_access_token" {
  sensitive = true
}

provider "azuredevops" {
  org_service_url       = "https://dev.azure.com/demo-org"
  personal_access_token = sensitive(var.ado_personal_access_token)
}

resource "azurerm_resource_group" "rg-stapp-001" {
  name     = "rg-stapp-demo-001"
  location = "ukwest"

  tags = {
    environment = "demo"
    source      = "terraform"
  }
}

resource "azurerm_static_site" "stapp-dtvlinux-001" {
  name                = "stapp-dtvlinux-demo-001"
  location            = "West Europe"
  resource_group_name = azurerm_resource_group.rg-stapp-001.name
  sku_tier            = "Free"
  sku_size            = "Free"

  tags                = {
    environment = "demo"
    source      = "terraform"
  }
}

resource "azurerm_static_site_custom_domain" "stapp-dtvlinux-001" {
  static_site_id  = azurerm_static_site.stapp-dtvlinux-001.id
  domain_name     = "www.example.com"
  validation_type = "cname-delegation"
}

resource "azuredevops_variable_group" "var-grp-dtvlinux-001" {
  project_id   = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  name         = "azure-static-web-apps-dtvlinux-variable-group"
  allow_access = true
  description  = "Static Web App Variable Group"

  variable {
    name         = "AZURE_STATIC_WEB_APPS_API_TOKEN_DTVLINUX"
    secret_value = azurerm_static_site.stapp-dtvlinux-001.api_key
    is_secret    = true
  }
}

resource "azuredevops_git_repository_file" "repo-file-dtvlinux-001" {
  repository_id       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  file                = "azure-pipelines.yml"
  content             = <<-EOT
    trigger:
      - refs/heads/main

    pool:
      vmImage: ubuntu-latest

    variables:
      - group: ${azuredevops_variable_group.var-grp-dtvlinux-001.name}

    steps:
      - checkout: self
        submodules: true
      - task: AzureStaticWebApp@0
        inputs:
          app_location: 'site'
          skip_app_build: true
          skip_api_build: true
          verbose: true
          azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN_DTVLINUX)
  EOT
  branch              = "refs/heads/main"
  overwrite_on_create = true
}

resource "azuredevops_build_definition" "build-def-dtvlinux-001" {
  project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
  name       = "Static Web App Build Definition"

  ci_trigger {
    use_yaml = true
  }

  repository {
    repo_type   = "TfsGit"
    repo_id     = "xxxxxxxx-xxxx-xxxx-xxxx-xxxx"
    branch_name = "refs/heads/main"
    yml_path    = "azure-pipelines.yml"
  }

  variable_groups = [
    azuredevops_variable_group.var-grp-dtvlinux-001.id
  ]
}

output "static_site_default_hostname" {
  value = "${azurerm_static_site.stapp-dtvlinux-001.default_host_name}."
}

Support DTV Linux

Click on each book below to review & buy on Amazon. As an Amazon Associate, I earn from qualifying purchases.

NordVPN ®: Elevate your online privacy and security. Grab our Special Offer to safeguard your data on public Wi-Fi and secure your devices. I may earn a commission on purchases made through this link.