feat: Added DNS control to tofu

This commit is contained in:
2026-07-10 22:19:08 +01:00
parent e39e1637da
commit ae34a78a1d
9 changed files with 109 additions and 5 deletions
+27
View File
@@ -17,6 +17,26 @@ locals {
# IDs for every named key in var.ssh_key_names - installed on every server.
ssh_key_ids = [for k in data.hcloud_ssh_key.main : k.id]
# Every A record currently referenced by a Traefik Host() rule anywhere under
# services/ - kept in sync with the actual compose files, not aspirational.
# "dev.luke-else.co.uk" is a *website* served from prod (a dev-tagged build of
# luke-else.co.uk), not the dev server - don't confuse the two.
dns_records = [
{ zone = "luke-else.co.uk", name = "@", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "dev", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "metarius", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "traefik", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "status", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "bitwarden", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "rd", value = module.prod.ipv4 },
{ zone = "luke-else.co.uk", name = "git", value = module.dev.ipv4 },
{ zone = "luke-else.co.uk", name = "traefik.cicd", value = module.dev.ipv4 },
{ zone = "luke-else.co.uk", name = "vpn", value = module.vpn.ipv4 },
{ zone = "luke-else.co.uk", name = "traefik.vpn", value = module.vpn.ipv4 },
{ zone = "divine-couture.co.uk", name = "www", value = module.prod.ipv4 },
{ zone = "snexo.co.uk", name = "@", value = module.prod.ipv4 },
]
}
module "network" {
@@ -77,3 +97,10 @@ module "vpn" {
bootstrap_script = local.bootstrap_script
ssh_private_key_path = var.ssh_private_key_path
}
module "dns" {
source = "./modules/dns"
zone_names = var.dns_zones
records = local.dns_records
}
+28
View File
@@ -0,0 +1,28 @@
# One Hetzner DNS zone per domain in var.zone_names. Hetzner becomes
# authoritative for these once the domain's registrar NS records are pointed at
# the nameservers in the `nameservers` output - that step happens at whichever
# registrar the domain was bought through, and can't be done from here.
resource "hcloud_zone" "this" {
for_each = toset(var.zone_names)
name = each.value
mode = "primary"
delete_protection = true # API-level guard, in addition to prevent_destroy below
lifecycle {
prevent_destroy = true
}
}
resource "hcloud_zone_rrset" "a" {
for_each = { for r in var.records : "${r.zone}/${r.name}" => r }
zone = hcloud_zone.this[each.value.zone].id
name = each.value.name
type = "A"
ttl = each.value.ttl
records = [
{ value = each.value.value }
]
}
+9
View File
@@ -0,0 +1,9 @@
output "zone_ids" {
description = "Map of domain name to Hetzner zone ID."
value = { for name, z in hcloud_zone.this : name => z.id }
}
output "nameservers" {
description = "Map of domain name to the nameservers Hetzner assigned it - point your registrar's NS records at these to delegate DNS."
value = { for name, z in hcloud_zone.this : name => z.authoritative_nameservers.assigned }
}
+15
View File
@@ -0,0 +1,15 @@
variable "zone_names" {
description = "Domains to create as Hetzner DNS zones."
type = list(string)
}
variable "records" {
description = "A records to create across the zones in var.zone_names. Use name = \"@\" for the zone apex."
type = list(object({
zone = string
name = string
value = string
ttl = optional(number, 300)
}))
default = []
}
+5
View File
@@ -17,3 +17,8 @@ output "prod_private_ipv4" {
output "vpn_ipv4" {
value = module.vpn.ipv4
}
output "dns_nameservers" {
description = "Nameservers Hetzner assigned each zone - point each domain's registrar NS records at these to delegate DNS to Hetzner."
value = module.dns.nameservers
}
+1
View File
@@ -24,3 +24,4 @@ ssh_private_key_path = "~/.ssh/id_ed25519"
# allowed_ssh_source_ips = ["203.0.113.4/32"]
# deploy_user = "deploy"
# dev_runner_count = 3
# dns_zones = ["luke-else.co.uk", "divine-couture.co.uk", "snexo.co.uk"]
+6
View File
@@ -97,3 +97,9 @@ variable "dev_runner_count" {
type = number
default = 1
}
variable "dns_zones" {
description = "Domains to manage as Hetzner DNS zones. Point each domain's registrar NS records at tofu output dns_nameservers for Hetzner to actually become authoritative."
type = list(string)
default = ["luke-else.co.uk", "divine-couture.co.uk", "snexo.co.uk"]
}
+1 -1
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
version = "~> 1.54" # hcloud_zone / hcloud_zone_rrset (DNS) require >= 1.54.0
}
local = {
source = "hashicorp/local"