How this site was built
9 minute read
The site itself
This is a static website using HUGO and the Docsy theme. I won’t go into detail about how this tech works as the websites below go into far better detail. But in short, I cloned the example site to my personal GitHub repo, then on my Windows workstation I cloned the repo locally so I could edit the configuration and add content via Visual Studio Code, with any changes checked back in GitHub.
Azure Static Web App
I use Azure Static Web Apps to host this site for two principal reasons. First, it has a free tier which is perfect for my consumption model. Secondly, I can integrate GitHub Actions into a CI/CD workflow so I can automate the publishing of new content by simply pushing new data to the GitHub repository. I’ll cover this action later.
I won’t detail how Terraform works as there are too many options and it’s not that relevant here. But I wil post my module and configuration code examples so you can be inspired.
This is the module code for the resource group
locals {
tags = {
app = var.prefix
env = var.environment
}
}
resource "azurerm_resource_group" "this" {
name = "rg-${var.prefix}-${var.environment}"
location = var.resource_group_location
tags = local.tags
}
variable "resource_group_location" {
type = string
description = <<EOT
(Required) Location of the resource group where the workload will be managed
Options:
- westeurope; west europe
- eastus; east us
- southeastasia; south east asia
EOT
validation {
condition = can(regex("^westeurope$|^eastus$|^southeastasia$|^west europe$|^east us$|^south east asia$", var.resource_group_location))
error_message = "Err: location is not valid."
}
}
variable "environment" {
type = string
description = <<EOT
(Required) Describe the environment type
Options:
- dev
- test
- prod
EOT
validation {
condition = can(regex("^dev$|^test$|^prod$", var.environment))
error_message = "Err: Valid options are dev, test, or prod."
}
}
variable "prefix" {
description = "(Required) Name of the workload"
type = string
}
output "resource_group_name" {
description = "The name of the resource group"
value = azurerm_resource_group.this.name
}
output "resource_group_location" {
description = "The location of the resource group"
value = azurerm_resource_group.this.location
}
This is the module code for the static app
locals {
tags = {
app = var.prefix
env = var.environment
}
}
resource "azurerm_static_site" "this" {
name = "stapp-${var.prefix}-${var.environment}"
resource_group_name = var.resource_group_name
location = var.resource_group_location
sku_size = var.sku_size
tags = local.tags
}
variable "resource_group_name" {
type = string
description = "(Required) Name of the resource group where the workload will be managed"
}
variable "resource_group_location" {
type = string
description = <<EOT
(Required) Location of the resource group where the workload will be managed
Options:
- westeurope; west europe
- eastus; east us
- southeastasia; south east asia
EOT
validation {
condition = can(regex("^westeurope$|^eastus$|^southeastasia$|^west europe$|^east us$|^south east asia$", var.resource_group_location))
error_message = "Err: location is not valid."
}
}
variable "environment" {
type = string
description = <<EOT
(Required) Describe the environment type
Options:
- dev
- test
- prod
EOT
validation {
condition = can(regex("^dev$|^test$|^prod$", var.environment))
error_message = "Err: Valid options are dev, test, or prod."
}
}
variable "prefix" {
description = "(Required) Name of the workload"
type = string
}
variable "sku_size" {
type = string
description = <<EOT
(Optional) Set the hosting plan
Free is recommened For hobby or personal projects.
Standard is or general purpose production apps
Options:
- Free
- Standard
EOT
default = "Free"
validation {
condition = can(regex("^Free$|^Standard$", var.sku_size))
error_message = "Err: Valid options are Free, Standard."
}
}
output "static_app_deployment_token" {
description = "This token is used by deployment workflows to authenticate with the Static Web App."
value = azurerm_static_site.this.api_key
}
This is the configuration code.
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.43.0"
}
}
}
provider "azurerm" {
features {}
}
locals {
prefix = "example-website"
location = "west europe"
environment = "dev"
}
module "resource_group" {
source = "app.terraform.io/resource_group/azurerm"
version = "~>0.1.0"
# insert required variables here
prefix = local.prefix
resource_group_location = local.location
environment = local.environment
}
output "resource_group_name" {
description = "The name of the resource group"
value = module.resource_group.resource_group_name
}
output "resource_group_location" {
description = "The location of the resource group"
value = module.resource_group.resource_group_location
}
module "static_site" {
source = "app.terraform.io/static_site/azurerm"
version = "~>0.1.0"
# insert required variables here
prefix = local.prefix
environment = local.environment
resource_group_name = module.resource_group.resource_group_name
resource_group_location = module.resource_group.resource_group_location
sku_size = "Free"
}
output "static_app_deployment_token" {
description = "This token is used by deployment workflows to authenticate with the Static Web App."
value = module.static_site.static_app_deployment_token
sensitive = true
}
At this point, you will have an empty but functioning website. The default landing page will be available on a randomized URL with SSL all configured for you. The kind of URL string could be something like https://green-smoke-0e35ce303.2.azurestaticapps.net/ with the key part being the TLD of azurestaticapps.net which is where all the static apps are hosted. We can use our own domain name, which I’ll cover later.
Web App Deployment Token
In the GitHub repository where your website is you need to create a secret called AZURE_STATIC_WEB_APPS_API_TOKEN
with the value of your static web app deployment token.
Reset deployment tokens in Azure Static Web Apps
Create the GitHub Workflow
In the GitHub repository create a new file in .github/workflows
with a suitable name, for example, deploy-web-app-to-azure.yaml
. On the referenced GitHub webpage you can copy their provided workflow direclty into your new file. It’s probably 99% ready to go. For my setup, I had to modify the workflow provided by the GitHub help page to solve some errors with Hugo and the output location.
Error using the direct copy from GitHub
These code samples should demonstrate what I had to modify to solve the error.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Deploy web app to Azure Static Web Apps
env:
APP_LOCATION: "/" # location of your client code
API_LOCATION: "api" # location of your api source code - optional
APP_ARTIFACT_LOCATION: "build" # location of client code build output
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
permissions:
issues: write
contents: read
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Build And Deploy
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: ${{ env.APP_LOCATION }}
api_location: ${{ env.API_LOCATION }}
app_artifact_location: ${{ env.APP_ARTIFACT_LOCATION }}
close:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close
steps:
- name: Close
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
action: "close"
// Added the following line to the bottom of the ENV variables
OUTPUT_LOCATION: "public"
// Add the following to the 'Build and Deploy' job
output_location: ${{ env.OUTPUT_LOCATION }}
// Added the following job to install Hugo dependency
# Sets up Hugo
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.63.2'
// Added the following command to build the site to the 'Build and Deploy' job
app_build_command: hugo --minify
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Deploy web app to Azure Static Web Apps
env:
APP_LOCATION: "/" # location of your client code
API_LOCATION: "api" # location of your api source code - optional
APP_ARTIFACT_LOCATION: "build" # location of client code build output
OUTPUT_LOCATION: "public"
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
permissions:
issues: write
contents: read
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy
steps:
# Sets up Hugo
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.63.2'
- uses: actions/checkout@v3
with:
submodules: true
- name: Build And Deploy
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: ${{ env.APP_LOCATION }}
api_location: ${{ env.API_LOCATION }}
output_location: ${{ env.OUTPUT_LOCATION }}
app_build_command: hugo --minify
close:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close
steps:
- name: Close
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
action: "close"
When you get a workflow to execute successfully you can see the code being imported to the Azure Static Web App in the portal such as below.
At this point, you will have a functioning website with content that is automatically uploaded when you make changes in GitHub. But the URL still is the Azure default which for you may be fine, if which case you’re pretty much done. But, if you want to personalize your site then follow the remaining steps.
Deploying to Azure Static Web App
Custom domain name
Again, I won’t get into details, but you need your public domain name and hosting provider with DNS services you can edit. If you want to use Azure DNS then go for it but I don’t so this will detail using my process. But the principle is the same, in that your create a DNS record in your domain that people use to ht your site on the public Internet, this record will then re-direct to the Azure website’s domain to reach your site.
Domain ownership validation
In the Azure portal, navigate to the website, and select the Custom domains
section. Here you’ll see your default URL. From the Add
menu choose Custom Domain on other DNS
. Enter your domain name in the Domain name
text box and select next
. You choose to create a validation reference as either TXT
or CNAME
, so check which one your DNS hosting partner supports, and select generate. Create the DNS record with the value provided by Azure and wait for it to be validated.
For example, a TXT record would be created
Type: TXT
Host: @
Value: sdfdasdgfasgimasdgmkgasdgdkmasdgkm
Add a www.domain.com
If you want to use a www.domain.com DNS record then in the Azure portal, in the Custom domains
section select Add
then enter the FQDN such as www.domain.com and select next
. The wizard will provide you with details to add to the DNS zone.
Type: CNAME
Host: www
Value: green-smoke-0e35ce303.2.azurestaticapps.net