Automating DNS with Terraform or Ansible

Automating DNS with Terraform or Ansible
Tutor Name:Pranay ShastriPublished at:December 12, 2025 at 03:43 PM

📋 Topic Synopsis

No excerpt available

Managing DNS records manually works fine when you have just a few domains, but what happens when you're responsible for hundreds of domains with thousands of records? Or when you need to make the same DNS changes across multiple environments consistently?

That's where DNS automation comes in. Tools like Terraform and Ansible let you manage DNS as code, making your infrastructure more reliable, repeatable, and scalable.

In this topic on DNS server, we'll explore how to automate DNS management using two popular tools, even if you've never automated anything before.

1. Why Automate DNS?

IaC Principles (Infrastructure as Code)

Infrastructure as Code means managing your IT resources through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.

For DNS, this means:

  • Storing DNS configurations in version-controlled files
  • Making changes through code reviews and pull requests
  • Having a complete history of all DNS changes
  • Enabling rollbacks when something goes wrong

Benefits of DNS as Code

Transformative advantages of treating DNS as code:

  • Version Control: Track every change with detailed commit messages
  • Collaboration: Multiple team members can safely work on DNS
  • Reproducibility: Exactly recreate environments anywhere
  • Disaster Recovery: Quickly restore DNS configurations
  • Compliance: Audit trails for regulatory requirements
  • Testing: Validate changes before deployment

Repeatability

Manual DNS management is prone to human error. Ever made a typo in an IP address or forgotten to update a record? With automation:

  • Every change follows the same tested process
  • Records are always created consistently
  • Complex multi-record changes happen atomically
  • Environments stay in sync automatically

Error Reduction Through Automation

How automation eliminates common DNS errors:

  • Syntax Validation: Automated checking catches typos
  • Consistency Enforcement: Templates ensure uniform records
  • Atomic Operations: All-or-nothing changes prevent partial updates
  • Rollback Capability: Instant recovery from failed changes

Scalability and Efficiency

Automation enables management of complex DNS infrastructures:

  • Bulk Operations: Update hundreds of records simultaneously
  • Template-Based Creation: Standardize record creation patterns
  • Cross-Environment Sync: Keep dev, staging, and prod consistent
  • Self-Service: Enable non-experts to make safe DNS changes

2. Comprehensive Terraform for DNS

Terraform is an infrastructure provisioning tool that uses declarative configuration files to manage resources across multiple providers.

Providers: AWS, Cloudflare, Azure, Google Cloud

Terraform supports DNS automation across major providers through specific providers:

AWS Route 53 Provider:

provider "aws" {
  region = "us-east-1"
  # Use AWS credentials from environment or ~/.aws/credentials
}

# Configure provider with specific credentials
provider "aws" {
  region     = var.aws_region
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
}

Cloudflare Provider:

provider "cloudflare" {
  email     = "[email protected]"
  api_key   = "your-global-api-key"
  # Or use API tokens (recommended)
  api_token = "your-api-token"
}

Azure Provider:

provider "azurerm" {
  features {}
  subscription_id = "your-subscription-id"
  tenant_id       = "your-tenant-id"
}

Google Cloud Provider:

provider "google" {
  project = "your-project-id"
  region  = "us-central1"
}

Creating DNS Zones

Create a DNS zone with Terraform:

Route 53 Example:

resource "aws_route53_zone" "primary" {
  name = "example.com"
  
  tags = {
    Environment = "production"
    Owner       = "dns-team"
    CreatedBy   = "terraform"
  }
}

# Private hosted zone
resource "aws_route53_zone" "private" {
  name = "internal.example.com"
  vpc {
    vpc_id = aws_vpc.main.id
  }
  
  tags = {
    Environment = "production"
    Visibility  = "private"
  }
}

Cloudflare Example:

resource "cloudflare_zone" "example" {
  zone = "example.com"
  plan = "free"
  
  lifecycle {
    prevent_destroy = true
  }
}

Azure Example:

resource "azurerm_dns_zone" "example" {
  name                = "example.com"
  resource_group_name = azurerm_resource_group.main.name
  
  tags = {
    environment = "production"
  }
}

Google Cloud Example:

resource "google_dns_managed_zone" "example" {
  name        = "example-zone"
  dns_name    = "example.com."
  description = "Managed zone for example.com"
  
  dnssec_config {
    state = "on"
  }
}

Creating Records

Once you have a zone, create DNS records:

Simple A Record:

resource "aws_route53_record" "www" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "www.example.com"
  type    = "A"
  ttl     = "300"
  records = ["192.0.2.1"]
  
  lifecycle {
    create_before_destroy = true
  }
}

MX Record:

resource "aws_route53_record" "mx" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "example.com"
  type    = "MX"
  ttl     = "300"
  records = [
    "10 mail.example.com",
    "20 backup-mail.example.com"
  ]
}

Multiple Records:

resource "aws_route53_record" "web_servers" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "example.com"
  type    = "A"
  ttl     = "300"
  records = [
    "192.0.2.1",
    "192.0.2.2",
    "192.0.2.3"
  ]
}

Complex Record Types:

# CNAME Record
resource "aws_route53_record" "cdn" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "cdn.example.com"
  type    = "CNAME"
  ttl     = "300"
  records = ["example-cloudfront.cloudfront.net"]
}

# TXT Record for SPF
resource "aws_route53_record" "spf" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "example.com"
  type    = "TXT"
  ttl     = "300"
  records = ["v=spf1 include:_spf.google.com ~all"]
}

# SRV Record
resource "aws_route53_record" "sip" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "_sip._tcp.example.com"
  type    = "SRV"
  ttl     = "300"
  records = ["10 100 5060 sipserver.example.com"]
}

Advanced Terraform DNS Patterns

Module-Based Approach:

# modules/dns-zone/main.tf
variable "domain_name" {
  description = "The domain name for the DNS zone"
  type        = string
}

variable "environment" {
  description = "Environment tag"
  type        = string
}

resource "aws_route53_zone" "zone" {
  name = var.domain_name
  
  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

output "zone_id" {
  value = aws_route53_zone.zone.zone_id
}

# Usage
module "production_dns" {
  source      = "./modules/dns-zone"
  domain_name = "example.com"
  environment = "production"
}

Dynamic Record Creation:

variable "web_servers" {
  description = "Map of web server names to IP addresses"
  type        = map(string)
  default = {
    web1 = "192.0.2.1"
    web2 = "192.0.2.2"
    web3 = "192.0.2.3"
  }
}

resource "aws_route53_record" "web_servers" {
  for_each = var.web_servers
  
  zone_id = aws_route53_zone.primary.zone_id
  name    = "${each.key}.example.com"
  type    = "A"
  ttl     = "300"
  records = [each.value]
}

Terraform State Management

Remote State Configuration:

terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "dns/production.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

State Locking:

# Prevent concurrent state modifications
terraform {
  backend "azurerm" {
    storage_account_name = "terraformstatesa"
    container_name       = "tfstate"
    key                  = "dns.production.tfstate"
  }
}

3. Comprehensive Ansible for DNS

Ansible is an automation tool that uses YAML playbooks to configure systems and manage resources.

Using DNS Modules

Ansible has specific modules for different DNS providers:

Route 53 Module:

- name: Create DNS record in Route 53
  route53:
    state: present
    zone: example.com
    record: www.example.com
    type: A
    ttl: 300
    value: 192.0.2.1
    overwrite: yes
    wait: yes

Cloudflare Module:

- name: Create DNS record in Cloudflare
  cloudflare_dns:
    zone: example.com
    record: www
    type: A
    value: 192.0.2.1
    account_api_token: "{{ cloudflare_api_token }}"
    proxied: yes

Azure DNS Module:

- name: Create DNS record in Azure
  azure_rm_dnsrecordset:
    resource_group: myResourceGroup
    zone_name: example.com
    relative_name: www
    record_type: A
    records:
      - entry: 192.0.2.1
    time_to_live: 300

Advanced Ansible DNS Playbooks

Complex Record Management:

---
- name: Manage DNS Records
  hosts: localhost
  gather_facts: no
  vars:
    dns_records:
      - name: www
        type: A
        value: 192.0.2.1
      - name: mail
        type: A
        value: 192.0.2.2
      - name: "@"
        type: MX
        value: "10 mail.example.com"
  
  tasks:
    - name: Create DNS records
      route53:
        state: present
        zone: example.com
        record: "{{ item.name }}.example.com"
        type: "{{ item.type }}"
        ttl: 300
        value: "{{ item.value }}"
      loop: "{{ dns_records }}"
      when: item.name != "@"
      
    - name: Create root records
      route53:
        state: present
        zone: example.com
        record: "example.com"
        type: "{{ item.type }}"
        ttl: 300
        value: "{{ item.value }}"
      loop: "{{ dns_records }}"
      when: item.name == "@"

Updating Records Programmatically

Dynamic Inventory Example:

- name: Update DNS with server IPs
  route53:
    state: present
    zone: example.com
    record: "{{ item.name }}.example.com"
    type: A
    ttl: 300
    value: "{{ item.ip }}"
  loop:
    - { name: "web1", ip: "192.0.2.1" }
    - { name: "web2", ip: "192.0.2.2" }

Integration with Dynamic Inventory:

- name: Update DNS from dynamic inventory
  route53:
    state: present
    zone: example.com
    record: "{{ inventory_hostname }}.example.com"
    type: A
    ttl: 300
    value: "{{ ansible_default_ipv4.address }}"
  delegate_to: localhost

Ansible Roles for DNS Management

Role Structure:

roles/dns_manager/
├── tasks/
│   ├── main.yml
│   ├── create_zone.yml
│   └── create_records.yml
├── vars/
│   └── main.yml
├── defaults/
│   └── main.yml
└── templates/
    └── zone_file.j2

Main Tasks File:

# roles/dns_manager/tasks/main.yml
---
- name: Include zone creation tasks
  include_tasks: create_zone.yml
  when: create_zone is defined and create_zone

- name: Include record creation tasks
  include_tasks: create_records.yml
  when: dns_records is defined

4. CI/CD Integration

Auto-Updating DNS on Deployment

GitHub Actions Example:

name: Deploy and Update DNS
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  TF_VERSION: 1.0.0
  AWS_REGION: us-east-1

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v2
      
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: ${{ env.TF_VERSION }}
        
    - name: Terraform fmt
      run: terraform fmt -check
      
    - name: Terraform Init
      run: terraform init
      
    - name: Terraform Validate
      run: terraform validate
      
    - name: Terraform Plan
      run: terraform plan -no-color
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

  deploy:
    needs: validate
    runs-on: ubuntu-latest
    environment: production
    steps:
    - name: Checkout code
      uses: actions/checkout@v2
      
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      
    - name: Terraform Init
      run: terraform init
      
    - name: Terraform Apply
      run: terraform apply -auto-approve
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        
    - name: Post-deployment DNS validation
      run: |
        # Wait for DNS propagation
        sleep 60
        # Validate DNS records
        dig www.example.com +short

GitLab CI Example:

stages:
  - validate
  - deploy
  - verify

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/terraform/dns

validate:
  stage: validate
  script:
    - cd ${TF_ROOT}
    - terraform fmt -check
    - terraform init
    - terraform validate
    - terraform plan -no-color
  only:
    - merge_requests
    - main

deploy:
  stage: deploy
  script:
    - cd ${TF_ROOT}
    - terraform init
    - terraform apply -auto-approve
  only:
    - main
  environment:
    name: production

verify:
  stage: verify
  script:
    - sleep 60
    - dig www.example.com +short | grep -q "192.0.2.1"
  only:
    - main

Validation Steps

Always validate DNS changes:

# Check that records were created
terraform show

# Test DNS resolution
dig www.example.com

# Verify record types
dig example.com MX

# Check TTL values
dig example.com | grep "TTL"

# Validate DNSSEC (if enabled)
dig +dnssec example.com

# Test from multiple locations
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9; do
    echo "Testing $resolver:"
    dig @$resolver www.example.com +short
done

Automated Testing

Unit Testing with Terratest:

# Test DNS record creation
func TestRoute53Records(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../examples/dns",
    }
    
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    
    # Verify A record
    record := GetRoute53Record(t, "example.com", "www.example.com", "A")
    assert.Equal(t, "192.0.2.1", record.Value)
}

5. DNS Version Control Best Practices

Repository Structure

Organize your DNS code logically:

dns-infrastructure/
├── production/
│   ├── main.tf
│   ├── variables.tf
│   ├── terraform.tfvars
│   └── backend.tf
├── staging/
│   ├── main.tf
│   ├── variables.tf
│   ├── terraform.tfvars
│   └── backend.tf
├── development/
│   ├── main.tf
│   ├── variables.tf
│   └── terraform.tfvars
├── modules/
│   └── dns-zone/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── scripts/
│   ├── validate-dns.sh
│   └── monitor-propagation.py
└── README.md

State Management

Use remote state for collaboration:

terraform {
  backend "s3" {
    bucket         = "mycompany-terraform-state"
    key            = "dns/production.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
    
    # State locking to prevent corruption
    workspace_key_prefix = "dns"
  }
}

Change Management

Implement proper change management:

  1. Branch for changes: Create feature branches for DNS modifications
  2. Peer review: Have teammates review DNS changes
  3. Automated testing: Validate syntax and plan changes
  4. Gradual rollout: Apply to staging first, then production
  5. Post-deployment validation: Verify changes worked as expected

Pull Request Template

## DNS Change Request

### Description
[Brief description of the DNS changes]

### Records Affected
- [ ] A records
- [ ] CNAME records
- [ ] MX records
- [ ] TXT records

### Impact Assessment
- [ ] No user impact
- [ ] Temporary downtime expected
- [ ] Performance improvements

### Testing Plan
- [ ] Validate in staging environment
- [ ] Check DNS propagation
- [ ] Verify application functionality

### Rollback Plan
[Steps to revert these changes if needed]

Security Best Practices

Credential Management:

# Use variables for sensitive data
variable "cloudflare_api_token" {
  description = "Cloudflare API token"
  type        = string
  sensitive   = true
}

# Never hardcode credentials
# Use environment variables or secure vaults

Access Control:

# Restrict state access
terraform {
  backend "gcs" {
    bucket  = "terraform-state-bucket"
    prefix  = "dns/"
    
    # Use service account with minimal permissions
  }
}

6. Monitoring and Alerting

DNS Health Monitoring

# Ansible playbook for DNS monitoring
- name: Monitor DNS Records
  hosts: localhost
  tasks:
    - name: Check DNS resolution
      shell: dig {{ item }} +short
      register: dns_result
      failed_when: dns_result.stdout == ""
      loop:
        - www.example.com
        - mail.example.com
        - api.example.com
      
    - name: Alert on DNS failures
      slack:
        token: "{{ slack_token }}"
        msg: "DNS resolution failed for {{ item.item }}"
      when: item.failed
      loop: "{{ dns_result.results }}"

Performance Monitoring

#!/bin/bash
# Monitor DNS query performance
resolvers=("8.8.8.8" "1.1.1.1" "9.9.9.9")
domain="example.com"

for resolver in "${resolvers[@]}"; do
    start_time=$(date +%s%N)
    dig @$resolver $domain >/dev/null 2>&1
    end_time=$(date +%s%N)
    duration=$((($end_time - $start_time) / 1000000))
    echo "Resolver $resolver: ${duration}ms"
done

7. Summary & Key Takeaways

DNS automation transforms DNS management from a manual, error-prone process into a reliable, scalable infrastructure component. Here are the essential points to remember:

  1. Infrastructure as Code: Treat DNS configurations as version-controlled code
  2. Tool Selection: Choose Terraform for declarative management or Ansible for procedural automation
  3. Provider Integration: Leverage native cloud provider integrations
  4. CI/CD Pipeline: Integrate DNS changes into deployment workflows
  5. Version Control: Maintain organized repositories with proper state management
  6. Security: Implement secure credential handling and access controls
  7. Monitoring: Continuously monitor DNS health and performance
  8. Best Practices: Follow established patterns for reliability and collaboration

Whether you choose Terraform for its declarative approach or Ansible for its procedural flexibility, automating your DNS management pays dividends in reliability and operational efficiency.

As organizations grow and DNS complexity increases, automation becomes not just beneficial but essential. By implementing these practices, you can ensure your DNS infrastructure scales with your organization while maintaining the reliability and security required for modern digital operations.

The investment in DNS automation pays off through reduced errors, faster deployments, improved security, and better collaboration among team members. Start small with basic record management and gradually expand to more sophisticated automation patterns as your comfort and requirements grow.