Azure
Azure plays two distinct roles in devhome: a Terraform-managed footprint
(terraform/azure/) covering DNS, offsite backup storage, this documentation site's
hosting, and — since the Entra ID identity work — the tenant-side identity platform
every app in the lab authenticates against; and a larger hybrid architecture (Hub VNet,
Azure Arc, private AKS, ACR) reached over the IPsec VPN tunnel — see
Architecture Overview for where that sits in the overall
topology.
What's actually in terraform/azure/
No longer the small two-resource footprint it started as — four distinct things now live here, each with its own file and its own reason for being in this stack rather than somewhere else:
DNS zone (devhome.cloud)
resource "azurerm_dns_zone" "devhome_dns_zone" {
name = "devhome.cloud"
resource_group_name = azurerm_resource_group.dns.name
}
Azure Public DNS is authoritative for devhome.cloud (name servers delegated from the
registrar). This is also the zone pfSense's ACME package validates against via
DNS-01 — see ACME Certificates.
One dynamic A record lives here: openvpn.devhome.cloud, pointed at the home
connection's public IP. Since that IP isn't static, it's kept current by an Azure
DevOps pipeline (see Pipelines) that looks up the current
public IP and calls az network dns record-set a add-record on a schedule — the same
public-IP lookup pattern (dig +short myip.opendns.com @resolver1.opendns.com) is
reused directly in terraform/azure/plan.sh so a manual terraform plan always has
the current address too.
Offsite backup storage account
A single storage account (devhomesa<random-suffix> — storage account names must be
globally unique across all of Azure, hence the random suffix) with two purposes:
backupscontainer — offsite copies of config backups produced byansible/backup-playbook.yml, written asbackups/devhome_backup_<YYYY-MM-DD>/<file>. A lifecycle management policy prunes anything older than 30 days, scoped specifically to that prefix so it can never touch anything else in the account.cloudshellfile share — Azure Cloud Shell's persistent$HOME, sized to Cloud Shell's own 5 GiB default so it can be selected as the existing share during Cloud Shell's setup wizard rather than provisioning a second one.
Entra ID identity platform (entra.tf)
By far the largest thing in this directory now — every OIDC app registration in the lab
(ArgoCD, Rancher,
Vault, vCenter) plus the
break-glass accounts Cloud Sync and interactive portal access both depend on. This gets
its own dedicated page rather than being described here — see Entra
for the tenant-side setup and My Apps for how each app ends up
launchable from myapps.microsoft.com. On-Prem Active Directory
covers the on-prem half this all builds on.
Lives in terraform/azure/ (not its own stack) because it authenticates via a second,
separate provider block in this same directory — see Credentials below.
Documentation site hosting (docs-static-site.tf)
This documentation site itself — docs.devhome.cloud — is an Azure Static Web App
(Free tier), chosen over the earlier Storage-static-website + CDN + Key Vault BYOC-cert
design: classic Azure CDN no longer accepts new profile creation on this API version,
and Front Door's ~$35/mo minimum wasn't worth it for a personal docs site. Static Web
Apps' free tier does the same job at $0/mo, at the cost of using Azure's own managed
certificate for this one domain instead of the lab's own *.devhome.cloud wildcard.
Terraform only provisions the Static Web App and its custom domain/DNS binding once — deploying content is a separate step, run every time the docs change:
builds the site with mkdocs and pushes it via the Azure Static Web Apps CLI, using a
deployment token this stack generates and publishes to devhome/docs-site in Vault
(the one thing terraform/azure writes to Vault rather than only reading from — a
resource publishing its own derived credential for a downstream consumer, same pattern
entra.tf uses for devhome/entra-sso).
Credentials
Two separate credential sets, two separate provider blocks — terraform/azure/provider.tf:
| Provider | Vault path | Scope |
|---|---|---|
azurerm |
devhome/azure (arm_subscription_id/arm_client_id/arm_client_secret/arm_tenant_id) |
DNS, storage, Static Web App — general Azure Resource Manager access |
azuread |
devhome/entra (entra_client_id/entra_client_secret/entra_tenant_id) |
Entra ID app registrations/groups only — a separate app scoped to Microsoft Graph (Application.ReadWrite.All, Group.ReadWrite.All, etc.), not the ARM service principal above. See Entra for how this app itself gets bootstrapped. |
Both are read via vault_kv_secret_v2 data sources, same pattern as every other
Terraform stack (see Vault Consumers). The backup storage
account/container names are themselves stored back into devhome/azure by
terraform/vault's state — this directory only ever reads that secret, never writes
it, so there's exactly one owner of that value.
The hybrid pieces (Hub VNet, Arc, AKS, ACR)
The Hub Resource Group side of the architecture — Virtual Network Gateway, Hub VNet
(172.16.0.0/16), Azure DNS Resolver, Arc-connected Kubernetes/Machines, private AKS,
and private ACR — is reached from the lab via the IPsec tunnel (see
VPN) and pfSense's Domain Overrides for the
private-link DNS zones. This is not managed from terraform/azure/ in this repo.