Send, receive, and shield emails with PostScale. One API, EU-hosted. PostScale

    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:

    1. Terraform installed (version 1.0 or later) - Download Terraform
    2. A DNScale account with an API key - Get your API key
    3. 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 init

    Creating 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 apply simultaneously 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 apply prevents 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

    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