Managing DNS with Terraform
Learn how to manage your DNS zones and records as Infrastructure as Code using the DNScale Terraform Provider.
What you'll learn
- Install and configure the DNScale Terraform provider with secure credential management
- Manage DNS zones, records, and DNSSEC keys as Terraform resources with proper state management
- Build reusable Terraform modules for common DNS patterns like websites, email, and multi-environment setups
- Integrate Terraform DNS management into CI/CD pipelines with remote state and workspace isolation
Managing DNS through code brings the same benefits you get from infrastructure as code everywhere else: version control, peer review, automated deployments, and reproducible environments. The DNScale Terraform Provider lets you manage zones, records, and DNSSEC configuration using HashiCorp Terraform.
For an alternative IaC approach using JavaScript-based configuration, see the DNSControl Guide.
Prerequisites
Before you begin, ensure you have:
- Terraform installed (version 1.0 or later) - Download Terraform
- A DNScale account with an API key - Get your API key
- Basic familiarity with Terraform concepts (providers, resources, state)
Provider Installation
The DNScale provider is published to the Terraform Registry. Add it to your Terraform configuration:
terraform {
required_providers {
dnscale = {
source = "dnscaleou/dnscale"
version = "~> 1.0"
}
}
}
provider "dnscale" {
api_key = var.dnscale_api_key
# Optional: api_url defaults to https://api.dnscale.eu
}Create a variables file to manage your API key securely:
# variables.tf
variable "dnscale_api_key" {
description = "DNScale API key"
type = string
sensitive = true
}Set the API key via environment variable:
export TF_VAR_dnscale_api_key="your-api-key-here"
terraform initCreating Your First Zone
A DNS zone represents a domain you want to manage. Create one with the dnscale_zone resource:
resource "dnscale_zone" "example" {
name = "example.com"
region = "eu"
}
output "zone_id" {
value = dnscale_zone.example.id
}
output "nameservers" {
value = dnscale_zone.example.nameservers
}Run terraform apply to create the zone. The output will show your assigned nameservers ā update these at your domain registrar to activate DNS hosting. The SOA and apex NS records are created automatically by DNScale and are not managed through Terraform.
Managing DNS Records
A Records
Point your domain to an IPv4 address. See What Is an A Record for details:
resource "dnscale_record" "www" {
zone_id = dnscale_zone.example.id
name = "www"
type = "A"
content = "192.0.2.1"
ttl = 3600
}
# Apex domain (root)
resource "dnscale_record" "apex" {
zone_id = dnscale_zone.example.id
name = ""
type = "A"
content = "192.0.2.1"
ttl = 3600
}AAAA Records
Add IPv6 support alongside your A records. See IPv6 vs IPv4 for why dual-stack DNS matters:
resource "dnscale_record" "www_ipv6" {
zone_id = dnscale_zone.example.id
name = "www"
type = "AAAA"
content = "2001:db8::1"
ttl = 3600
}CNAME Records
Create aliases for subdomains. Note the CNAME vs A record trade-offs:
resource "dnscale_record" "blog" {
zone_id = dnscale_zone.example.id
name = "blog"
type = "CNAME"
content = "example.github.io."
ttl = 3600
}MX Records
Configure email delivery:
resource "dnscale_record" "mx_primary" {
zone_id = dnscale_zone.example.id
name = ""
type = "MX"
content = "mail.example.com."
ttl = 3600
priority = 10
}
resource "dnscale_record" "mx_backup" {
zone_id = dnscale_zone.example.id
name = ""
type = "MX"
content = "mail2.example.com."
ttl = 3600
priority = 20
}TXT Records
Add SPF, DKIM, or verification records:
# SPF record
resource "dnscale_record" "spf" {
zone_id = dnscale_zone.example.id
name = ""
type = "TXT"
content = "v=spf1 include:_spf.google.com ~all"
ttl = 3600
}
# Domain verification
resource "dnscale_record" "verification" {
zone_id = dnscale_zone.example.id
name = ""
type = "TXT"
content = "google-site-verification=abc123..."
ttl = 3600
}CAA Records
Control which certificate authorities can issue SSL/TLS certificates for your domain:
resource "dnscale_record" "caa_issue" {
zone_id = dnscale_zone.example.id
name = ""
type = "CAA"
content = "0 issue \"letsencrypt.org\""
ttl = 3600
}
resource "dnscale_record" "caa_issuewild" {
zone_id = dnscale_zone.example.id
name = ""
type = "CAA"
content = "0 issuewild \"letsencrypt.org\""
ttl = 3600
}SRV Records
Define service locations:
resource "dnscale_record" "sip" {
zone_id = dnscale_zone.example.id
name = "_sip._tcp"
type = "SRV"
content = "sip.example.com."
ttl = 3600
priority = 10
}TLSA Records
Associate TLS certificates with domain names for DANE:
resource "dnscale_record" "tlsa_web" {
zone_id = dnscale_zone.example.id
name = "_443._tcp"
type = "TLSA"
content = "3 1 1 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
ttl = 3600
}PTR Records
Create reverse DNS entries:
resource "dnscale_record" "ptr" {
zone_id = dnscale_zone.example.id
name = "1"
type = "PTR"
content = "server.example.com."
ttl = 3600
}Enabling DNSSEC
Secure your zone with DNSSEC by creating signing keys:
# Key Signing Key (KSK)
resource "dnscale_dnssec_key" "ksk" {
zone_id = dnscale_zone.example.id
key_type = "ksk"
algorithm = 13
bits = 256
active = true
published = true
}
# Zone Signing Key (ZSK)
resource "dnscale_dnssec_key" "zsk" {
zone_id = dnscale_zone.example.id
key_type = "zsk"
algorithm = 13
bits = 256
active = true
published = true
}After applying, retrieve the DS record to configure at your registrar:
output "ds_record" {
value = dnscale_dnssec_key.ksk.ds
}Using Data Sources
Query existing resources without managing them:
List All Zones
data "dnscale_zones" "all" {}
output "all_zones" {
value = data.dnscale_zones.all.zones
}List Records in a Zone
data "dnscale_records" "all" {
zone_id = dnscale_zone.example.id
}
# Filter to find specific records
output "a_records" {
value = [for r in data.dnscale_records.all.records : r if r.type == "A"]
}Check DNSSEC Status
data "dnscale_dnssec_status" "check" {
zone_id = dnscale_zone.example.id
}
output "dnssec_enabled" {
value = data.dnscale_dnssec_status.check.enabled
}Importing Existing Resources
Already have zones configured in DNScale? Import them into Terraform:
# Import a zone
terraform import dnscale_zone.existing <zone-id>
# Import a record
terraform import dnscale_record.existing <zone-id>/<record-id>
# Import a DNSSEC key
terraform import dnscale_dnssec_key.existing <zone-id>/<key-id>Find IDs in the DNScale dashboard or via the API. You can also use zone import methods to migrate records from another provider first, then import them into Terraform state.
State Management
Terraform state tracks the relationship between your configuration and the real resources. For DNS management, state handling requires extra care because DNS changes are globally visible.
Remote State Storage
For team collaboration, always use remote state. This prevents conflicting DNS changes from team members running Terraform simultaneously:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "dns/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}Always enable state locking (via DynamoDB for S3 backends). Without locking, two team members could run
terraform applysimultaneously and create conflicting DNS records. A misconfigured A record or deleted MX record could mean downtime or lost email.
State Drift
DNS records can be modified outside Terraform ā through the dashboard, API, or DNSControl. Run terraform plan regularly to detect drift between your configuration and reality. The DNScale provider will show any differences and let you decide whether to reconcile.
Best Practices
Use Variables for Reusability
variable "domain" {
description = "Domain name to manage"
type = string
}
variable "web_servers" {
description = "List of web server IPs"
type = list(string)
}
resource "dnscale_zone" "main" {
name = var.domain
region = "eu"
}
resource "dnscale_record" "web" {
count = length(var.web_servers)
zone_id = dnscale_zone.main.id
name = "www"
type = "A"
content = var.web_servers[count.index]
ttl = 300
}Organize with Modules
Create reusable modules for common patterns:
# modules/website-dns/main.tf
resource "dnscale_zone" "zone" {
name = var.domain
region = var.region
}
resource "dnscale_record" "apex" {
zone_id = dnscale_zone.zone.id
name = ""
type = "A"
content = var.server_ip
ttl = var.ttl
}
resource "dnscale_record" "www" {
zone_id = dnscale_zone.zone.id
name = "www"
type = "CNAME"
content = "${var.domain}."
ttl = var.ttl
}Use Workspaces for Environments
terraform workspace new staging
terraform workspace new production
# Switch between environments
terraform workspace select staging
terraform apply -var-file="staging.tfvars"Set Appropriate TTLs
Choose TTL values based on record stability. Production records that rarely change should have higher TTLs to reduce query volume:
# Stable record - long TTL
resource "dnscale_record" "mx" {
zone_id = dnscale_zone.main.id
name = ""
type = "MX"
content = "aspmx.l.google.com."
ttl = 86400 # 24 hours
priority = 1
}
# Dynamic record - short TTL
resource "dnscale_record" "api" {
zone_id = dnscale_zone.main.id
name = "api"
type = "A"
content = "203.0.113.51"
ttl = 300 # 5 minutes
}CI/CD Integration
Automate DNS changes through your CI/CD pipeline for safe, reviewable deployments:
# .github/workflows/dns.yml
name: DNS Infrastructure
on:
pull_request:
paths: ["dns/**"]
push:
branches: [main]
paths: ["dns/**"]
jobs:
plan:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: dns
- name: Terraform Plan
run: terraform plan -no-color
working-directory: dns
env:
TF_VAR_dnscale_api_key: ${{ secrets.DNSCALE_API_KEY }}
apply:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: dns
- name: Terraform Apply
run: terraform apply -auto-approve
working-directory: dns
env:
TF_VAR_dnscale_api_key: ${{ secrets.DNSCALE_API_KEY }}Use GitHub Environments with required reviewers for the apply step. DNS changes are global and difficult to "undo" quickly because of TTL caching. A human review gate before
terraform applyprevents accidental deletions or misconfigurations.
Complete Example
Here's a full configuration for a typical website:
terraform {
required_providers {
dnscale = {
source = "dnscaleou/dnscale"
version = "~> 1.0"
}
}
}
provider "dnscale" {
api_key = var.dnscale_api_key
}
variable "dnscale_api_key" {
type = string
sensitive = true
}
# Create zone
resource "dnscale_zone" "main" {
name = "mywebsite.com"
region = "eu"
}
# Website records
resource "dnscale_record" "apex" {
zone_id = dnscale_zone.main.id
name = ""
type = "A"
content = "203.0.113.50"
ttl = 3600
}
resource "dnscale_record" "www" {
zone_id = dnscale_zone.main.id
name = "www"
type = "CNAME"
content = "mywebsite.com."
ttl = 3600
}
# Email configuration
resource "dnscale_record" "mx" {
zone_id = dnscale_zone.main.id
name = ""
type = "MX"
content = "aspmx.l.google.com."
ttl = 3600
priority = 1
}
resource "dnscale_record" "spf" {
zone_id = dnscale_zone.main.id
name = ""
type = "TXT"
content = "v=spf1 include:_spf.google.com ~all"
ttl = 3600
}
# Enable DNSSEC
resource "dnscale_dnssec_key" "ksk" {
zone_id = dnscale_zone.main.id
key_type = "ksk"
algorithm = 13
bits = 256
active = true
published = true
}
resource "dnscale_dnssec_key" "zsk" {
zone_id = dnscale_zone.main.id
key_type = "zsk"
algorithm = 13
bits = 256
active = true
published = true
}
# Outputs
output "nameservers" {
value = dnscale_zone.main.nameservers
}
output "ds_record" {
value = dnscale_dnssec_key.ksk.ds
}Next Steps
- Browse the Provider Documentation for complete API reference
- Learn about DNS Record Types supported by DNScale
- Understand DNS Zones and how they work
- Explore Zone Import Methods to migrate existing DNS
- Set up Email Security with SPF, DKIM, and DMARC
- Compare with DNSControl for a JavaScript-based alternative
- Review DNS security best practices for your zones
Conclusion
The DNScale Terraform Provider brings Infrastructure as Code principles to DNS management. Version control your DNS changes, automate deployments across environments, and maintain consistency across your infrastructure. Whether you manage a single domain or hundreds, Terraform provides the tooling to do it reliably and at scale.
Ready to manage your DNS with confidence?
DNScale provides anycast DNS hosting with a global network, real-time analytics, and an easy-to-use API.
Start free