Creating DNS Records

Technical Background

DNS (Domain Name System) maps hostnames to IP addresses and provides aliases for easier access.

In this exercise, we use Terraform to:

  • Create A records pointing to a fixed IP (1.2.3.4).
  • Create CNAME alias records referencing the canonical hostname.
  • Ensure flexibility via variables and enforce configuration correctness through validation rules.

Reasons to use Terraform for DNS:

  • Automation: Avoid manual nsupdate commands.
  • Reusability: Define variables for domain, canonical host, and aliases.
  • Validation: Prevent alias conflicts (e.g., duplicate aliases or alias identical to canonical host).

Solution

Prerequisits

Create the same files and folder structure as done before in exercise 18 Enhancing The Web Server.

Configure DNS Provider

  1. Add required DNS provider to KnownHostsByModule/providers.tf:
terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
    }
    dns = {
      source  = "hashicorp/dns"
      version = "~> 3.0"
    }
  }
  required_version = ">= 0.13"
}
  1. Define the DNS provider in KnownHostsByModule/providers.tf:
provider "dns" {
  update {
    server        = "ns1.sdi.hdm-stuttgart.cloud"
    key_name      = "gXY.key."
    key_algorithm = "hmac-sha512"
    key_secret    = var.dns_secret
  }
}

Add DNS Secret

  1. Store your yout DNS Secret inside your secrets.auto.tfvars:
dns_secret = "<your-dns-secret>"

Warning

Make your it's always in a file that is version-controlled and published to the internet.

  1. Create dns_secret variable in /KnownHostsByModule/variables.tf:
variable "dns_secret" {
  type = string
  sensitive = true
}

Define Input Variables

  1. Create these variables in /KnownHostsByModule/variables.tf:
variable "dns_zone" {
  type        = string
  description = "DNS zone (must end with a dot)"
  validation {
    condition     = can(regex("\\.$", var.dns_zone))
    error_message = "dnsZone must end with a dot (e.g., g12.sdi.hdm-stuttgart.cloud.)"
  }
}

variable "server_ip" {
  type        = string
  description = "IPv4 address of the server"
}

variable "server_name" {
  type        = string
  description = "Canonical server name"
}

variable "server_aliases" {
    type        = list(string)
    description = "A list of aliases (CNAMEs) pointing to the server"
    validation {    
        error_message = "Aliases must be unique and must not match the canonical server name."
        condition     = length(distinct(var.server_aliases)) == length(var.serverAliases) && !contains(var.serverAliases, var.serverName)
  }
}

Note

The validation attributes stops DNS update failures.

  1. Create /KnownHostsByModule/config.auto.tfvars and configure the variables:
server_ip      = "1.2.3.4"
dns_zone       = "g11.sdi.hdm-stuttgart.cloud."
server_name    = "workhorse"
server_aliases = ["www", "mail"]

Warning

Put config.auto.tfvars in your .gitignore if it contains sensitive or environment-specific data, such as:

  • API keys (dns_secret via environment variables, if accidentally written in the file)
  • Server IP addresses that are temporary or private
  • Group-specific DNS zones (if not intended for public)

Create DNS Records

  1. Create /KnownHostsByModule/dns-records.tf containing:
resource "dns_a_record_set" "canonical" {
    zone        = var.dns_zone
    name        = var.server_name
    addresses   = [var.server_ip]
    ttl         = 10
}

resource "dns_a_record_set" "root" {
    zone        = var.dns_zone
    name        = var.server_name 
    addresses   = [var.server_ip]
    ttl         = 10
}

resource "dns_a_record_set" "aliases" {
    count       = length(var.server_aliases)
    zone        = var.dns_zone
    name        = var.server_aliases[count.index]
    addresses   = [var.server_ip]
    ttl         = 10
}

Warning

Duplicated names will cause Terraform to crash

  1. Add output to /KnownHostsByModule/outputs.tf:
output "server_fqdn" {
  description = "Fully qualified domain name of the canonical server"
  value       = "${var.server_name}.${var.dns_zone}"
}

output "alias_fqdns" {
  description = "All alias FQDNs"
  value       = [
    for alias in var.server_aliases : "${alias}.${var.dns_zone}"
  ]
}

Deploy and Verify

  1. Initialize and apply Terraform inside /KnownHostsByModule:
terraform init
terraform apply
  1. Verify with dig:
dig +noall +answer @ns1.hdm-stuttgart.cloud AXFR gxy.sdi.hdm-stuttgart.cloud

Success

Expected Output:

gxy.sdi.hdm-stuttgart.cloud. 10 IN A 1.2.3.4 workhorse.gxy.sdi.hdm-stuttgart.cloud. 10 IN A 1.2.3.4 www.gxy.sdi.hdm-stuttgart.cloud. 10 IN CNAME workhorse.gxy.sdi.hdm-stuttgart.cloud. mail.gxy.sdi.hdm-stuttgart.cloud. 10 IN CNAME workhorse.gxy.sdi.hdm-stuttgart.cloud.

Note

Changes might not appear immediately if DNS caches are still valid.

DNS

Terraform Variables

DNS Record Types

Terraform Count

Terraform Validation Rules

Terraform Distinct Function

Terraform Contains