How We Manage Domain and DNS Management with Infrastructure as Code
Managing hundreds of domains with consistency, compliance, and visibility is a often a challenge entirely. After successfully adopting Terraform for GitHub repository management, the next step in our Infrastructure as Code (IaC) journey was clear: dogfood our own product and manage our domains and DNS zones using the DNSimple Terraform provider.
This is the story of how we built a system on top of DNSimple Terraform provider to manage all our domains and DNS zones as code, dealing with the unique challenges of domain lifecycle management, and how this approach transformed our compliance posture and operational visibility.
From GitHub to DNS: The natural progression
Our journey with Infrastructure as Code at DNSimple didn't start with Terraform. We've been managing infrastructure as code since day one, primarily using Chef for configuration management. When we successfully adopted Terraform for repository management, we realized we had proven the model for managing external resources through IaC.
The success of infra-github gave us confidence. The workflow was solid: declarative configuration, pull request reviews, automated CI/CD through GitHub Actions and Terraform Cloud. Team members across different skill levels could contribute. Changes were visible, auditable, and reversible.
But there was something ironic about our situation. We were building and maintaining the DNSimple Terraform provider, yet we weren't using it ourselves for our own domain and DNS management. We were still managing many of our domains and zones through the web interface or API scripts.
The decision to build infra-dnsimple wasn't just about operational efficiency. It was about validation. If we couldn't manage our own infrastructure with our tools, how could we expect our customers to?

The unique challenge of domain management
DNS hosting is relatively straightforward in the Infrastructure as Code world. You define records, apply changes, and the system reflects your desired state. Many providers offer DNS management through Terraform.
Domain registration and lifecycle management is different. Unlike DNS record changes, which are typically real-time operations, domain operations often involve asynchronous processes. Domain registrations require communication with registries, transfers take days to complete, and DNSSEC enablement involves multi-step validation. These aren't instantaneous API calls—they're workflows with waiting periods, external dependencies, and state transitions.
DNSimple is currently the only provider offering complete domain lifecycle management in Terraform. This includes domain registration, renewal configuration, DNSSEC enablement, transfer lock management, delegation, and all the operational aspects of actually owning a domain, not just hosting its DNS.
This distinction matters. Most infrastructure teams managing DNS still handle domain registrations through manual processes or separate systems. This creates a disconnect: your DNS records are in version control, but your domain configurations, renewal settings, and security features are managed elsewhere.
We designed the DNSimple Terraform provider to bridge this gap. Let's look at how that works in practice.
Domain lifecycle management
Registering a domain
Domain registration in Terraform requires two resources: the domain itself and the registration metadata. Here's how we register dnsimple.dev:
resource "dnsimple_domain" "dnsimple_dev" {
provider = dnsimple.domains
name = "dnsimple.dev"
}
resource "dnsimple_registered_domain" "dnsimple_dev" {
provider = dnsimple.domains
name = dnsimple_domain.dnsimple_dev.name
contact_id = 1234
auto_renew_enabled = true
transfer_lock_enabled = true
whois_privacy_enabled = false
dnssec_enabled = true
}
The dnsimple_domain resource represents the domain in your DNSimple account. The dnsimple_registered_domain resource handles the actual registration and its configuration.
This separation is intentional. You might have domains in your DNSimple account that aren't registered through DNSimple (perhaps transferring in, or using DNSimple only for DNS). The two-resource model accommodates both scenarios.
In practice, you'll want to create a contact resource and reference it dynamically rather than hardcoding contact IDs:
resource "dnsimple_contact" "operations" {
provider = dnsimple.domains
label = "Operations Team"
first_name = "DNSimple"
last_name = "Operations"
organization_name = "Example Corporation"
job_title = "System Administrator"
address1 = "123 Main Street"
city = "San Francisco"
state_province = "CA"
postal_code = "94102"
country = "US"
phone = "+1.5551234567"
email = "ops@example.com"
}
resource "dnsimple_registered_domain" "dnsimple_dev" {
provider = dnsimple.domains
name = dnsimple_domain.dnsimple_dev.name
contact_id = dnsimple_contact.operations.id
auto_renew_enabled = true
transfer_lock_enabled = true
whois_privacy_enabled = false
dnssec_enabled = true
}
Using Terraform references ensures that contact updates automatically propagate to all domains referencing them, maintaining consistency across your domain portfolio.
Configuring domain settings
The registration resource exposes all the operational controls you need:
Auto-renewal ensures domains don't expire accidentally:
auto_renew_enabled = true
Transfer lock prevents unauthorized transfers:
transfer_lock_enabled = true
DNSSEC adds cryptographic authentication to DNS responses:
dnssec_enabled = true
WHOIS privacy protects registrant information (when you need it):
whois_privacy_enabled = true
For domains requiring extended attributes during registration (like .us requiring nexus category, or .fr requiring specific registrant types), you can specify them inline:
resource "dnsimple_registered_domain" "example_us" {
provider = dnsimple.domains
name = "example.us"
contact_id = 48255
auto_renew_enabled = true
transfer_lock_enabled = true
extended_attributes = {
"us_nexus" = "C11"
"us_purpose" = "P3"
}
}
Managing delegation
Name server delegation is configured separately, allowing you to delegate domains to any name servers, not just DNSimple's:
resource "dnsimple_domain_delegation" "dnsimple_dev" {
provider = dnsimple.domains
domain = dnsimple_domain.dnsimple_dev.name
name_servers = [
"ns1.dnsimple.com",
"ns2.dnsimple-edge.net",
"ns3.dnsimple.com",
"ns4.dnsimple-edge.org",
]
}
This delegation resource updates the name servers at the registry. It's declarative: change the list, apply the plan, and Terraform updates the delegation.
Managing DNS records
DNS record management is where most teams start with Infrastructure as Code. The DNSimple provider makes this straightforward.
Each domain gets its own zone file. Here's an excerpt from zones_dnsimple.com.tf:
resource "dnsimple_zone" "dnsimple_com" {
provider = dnsimple.core
name = "dnsimple.com"
}
resource "dnsimple_zone_record" "record_apex_a" {
provider = dnsimple.core
zone_name = dnsimple_zone.dnsimple_com.name
name = ""
type = "A"
value = "104.245.210.170"
ttl = 3600
}
resource "dnsimple_zone_record" "record_www_cname" {
provider = dnsimple.core
zone_name = dnsimple_zone.dnsimple_com.name
name = "www"
type = "CNAME"
value = "dnsimple.com"
ttl = 3600
}
resource "dnsimple_zone_record" "record_mx_primary" {
provider = dnsimple.core
zone_name = dnsimple_zone.dnsimple_com.name
name = ""
type = "MX"
value = "aspmx.l.google.com"
ttl = 3600
priority = 1
}
Each record is a separate resource with a unique identifier. To add a record, you add a resource. To remove a record, you delete the resource. Terraform handles the synchronization.
We agreed on a standard naming convention: one file per domain, named zones_<domain-name>.tf. Inside each file, records are sorted alphabetically by name, then type. This makes large zone files easier to navigate and reduces merge conflicts when multiple people work on different records.
Repository structure and workflow
The infra-dnsimple repository follows the same pattern we established with infra-github:
workspace/
├── contacts.tf
├── domains_dnsimple.com.tf
├── domains_dnsimple.dev.tf
├── zones_dnsimple.com.tf
├── zones_dnsimple.dev.tf
└── forwards_dnsimple.dev.tf
All Terraform configuration lives in the workspace directory. Each domain gets up to three files:
domains_<name>.tf- Domain and registration configurationzones_<name>.tf- DNS recordsforwards_<name>.tf- Email forwarding (when applicable)
The workflow mirrors infra-github:
- Create a branch and make changes
- Open a pull request
- GitHub Actions runs
terraform planand posts results as a PR comment - Team reviews the plan
- Merge the PR
- GitHub Actions automatically runs
terraform apply
This workflow democratizes DNS management. You don't need Terraform installed locally. You don't need to understand DNS internals deeply. The plan shows you exactly what will change in plain English.
The benefits we realized
Centralized management
Before Terraform, domain and DNS changes happened through the web interface or API scripts. There was no central inventory. No way to answer "what domains do we own?" without clicking through the interface or writing a custom script.
Now, the repository is the source of truth. Want to know what domains we manage? Look at the domains_*.tf files. Want to see DNS configuration for a specific domain? Open its zone file.
Bulk changes at scale
Need to update CAA records across all domains to add a new certificate authority? That's a single change in a script that generates the Terraform configuration:
# Add CAA record to all zone files
for zone in workspace/zones_*.tf; do
# Add CAA record resource
done
Or, more realistically, you can use Terraform's built-in capabilities to loop over resources and apply changes systematically.
Before Terraform, this would mean either manually updating each zone through the interface or writing a one-off script against the API.
Visibility and auditability
Every change goes through a pull request. Every change is reviewed. Every change is documented in Git history.
Need to know when we enabled DNSSEC on a domain? Check the Git history:
git log -p -- workspace/domains_dnsimple.com.tf | grep dnssec
Need to know who added a specific DNS record? Git blame shows you the commit, the author, and the pull request.
This visibility extends beyond the engineering team. Compliance auditors can review change history. Finance can track domain registrations. Operations can monitor configuration drift.
Preventing configuration drift
One of the most powerful aspects of Infrastructure as Code is drift detection. If someone makes a change through the web interface or API that doesn't match the Terraform state, the next terraform plan detects it.
We run Terraform plans on a schedule through GitHub Actions. Any drift triggers an alert. This ensures the repository remains the definitive source of truth.
Compliance and governance
Infrastructure as Code for domain and DNS management has a transformative impact on compliance. If you're pursuing ISO 27001, SOC 2, or similar certifications, IaC helps you demonstrate rigorous control over your infrastructure in ways that traditional approaches cannot.
The key compliance benefits include:
Access control - GitHub repository access and PR reviews replace direct admin access. Credentials are isolated in Terraform Cloud and GitHub Secrets, implementing true least privilege without bottlenecks.
Change management - Pull requests create an immutable audit trail automatically. Every change includes what changed, who proposed it, who approved it, when it was applied, and why—all required for compliance audits.
Configuration standards - Policies can be encoded and enforced automatically using tools like Terraform Sentinel or OPA, preventing violations before they occur rather than detecting them after the fact.
Asset inventory - The repository becomes your official asset inventory, always current and version controlled. During audits, you simply point to the Git repository rather than maintaining separate spreadsheets.
Segregation of duties - Branch protection rules enforce that the person who proposes a change cannot be the person who approves it, with technical controls rather than procedural documentation.
What we discovered by moving to IaC
Beyond the planned benefits, migrating our domains and DNS to Terraform revealed significant insights about our infrastructure.
Cleanup of unused records
When we started importing our existing DNS zones into Terraform, we discovered hundreds of records that were no longer needed:
- DNS records for decommissioned services
- Test records from debugging sessions years ago
- Duplicate records with different TTLs
- Records pointing to IP addresses we no longer controlled
These records accumulated over 14 years of operation. They didn't cause problems (DNS is forgiving), but they represented technical debt and potential security risks.
The process of codifying each record forced us to ask: "What is this for? Do we still need it?"
We removed over 200 obsolete DNS records during the initial migration. Each deletion went through a pull request with explanation and review. The cleanup was systematic and documented.

Configuration drift detection
We discovered several domains where settings had drifted from our standard configuration:
- Domains where DNSSEC wasn't enabled (but should have been)
- Transfer locks disabled on production domains
- Auto-renewal disabled on critical domains
- Name server delegation pointing to old infrastructure
Some of these were intentional but undocumented. Others were mistakes from years ago that we'd never noticed.
Terraform's plan output made the drift visible:
Terraform will perform the following actions:
# dnsimple_registered_domain.example will be updated in-place
~ dnssec_enabled = false -> true
~ auto_renew_enabled = false -> true
We could review each difference, determine if it was intentional or drift, and either update the configuration to match reality or fix the drift.
Standardization opportunities
Having all domains and zones in one repository made patterns obvious:
- Some domains were using our first-generation Anycast IP addresses (deprecated in 2016)
- Email forwarding configurations were inconsistent
- CAA records were missing from many zones
- DMARC policies varied across domains
With everything visible in one place, we could systematically address these inconsistencies. We created standard templates for common configurations and applied them across all applicable domains.
Documentation by default
The Terraform configuration became self-documenting infrastructure. New team members can understand our domain portfolio by reading the code. The configuration answers questions like:
- Why is WHOIS privacy disabled on this domain? (Required for certain TLDs)
- Why does this zone have these specific CAA records? (They correspond to our certificate issuance policy)
- Why are these domains delegated to different name servers? (Legacy configuration, migration in progress)
We added comments to the Terraform files to capture this context:
# dnsimple.com is managed separately for security reasons.
# The domain resource is not managed by Terraform to prevent
# accidental deletion. Only the registration settings are managed.
resource "dnsimple_registered_domain" "dnsimple_com" {
provider = dnsimple.core
name = "dnsimple.com"
# ...
}
Lessons learned
This migration taught us several important lessons:
Start with a manageable subset. We didn't try to import all domains at once. We started with test domains in our playground account, validated the workflow, then gradually imported production domains in batches.
Import is tedious but essential. Terraform's import command brings existing resources under management. For hundreds of domains and thousands of DNS records, this was time-consuming. We wrote scripts to generate the import commands and basic resource definitions, but human review was still required.
Resource naming matters. We defined a naming conventions early (domains_<name>.tf, zones_<name>.tf). This made the repository navigable and predictable. Consistency in resource names within files (dnsimple_zone.example_com, dnsimple_zone_record.record_12345) helped with automation.
Terraform state is critical. With hundreds of resources, state file management through Terraform Cloud was essential. Local state files would be unmanageable and create collaboration problems.
Documentation is code now. We moved domain documentation from wikis and tickets into code comments. If someone needs to know why a domain is configured a certain way, the answer is in the Terraform file.
The provider must be robust. Dogfooding revealed several edge cases in the DNSimple Terraform provider. We fixed bugs and improved error messages. Our customers benefit from the provider improvements driven by our internal use.
What's next
We're continuing to evolve our domain and DNS infrastructure management:
Provider abstraction improvements - Dogfooding revealed that certain aspects of the Terraform provider are too coupled to our internal architecture. We're working to improve these abstractions to provide a better user experience while maintaining power and flexibility.
Internal workflow adjustments - The migration revealed internal systems that still modify DNS records directly through the API, bypassing the IaC repository. We're working to have these systems open pull requests instead, ensuring all changes go through the same review workflow for full visibility and auditing.
Conclusion
Moving from manual domain and DNS management to Infrastructure as Code was transformative. The benefits go beyond operational efficiency. We have better compliance, better visibility, better standards enforcement, and better collaboration. This approach aligns with our broader commitment to domain management automation across all infrastructure.
The irony that we weren't using our own Terraform provider for our own infrastructure pushed us to take this step. The result validated not just the technical approach, but the product itself. We can confidently tell customers: "We use this in production for managing our own infrastructure."
If you're managing more than a handful of domains, or if you're pursuing compliance certifications, Infrastructure as Code for domain and DNS management isn't just beneficial, it's essential. The DNSimple Terraform provider makes this possible with full domain lifecycle management, not just DNS hosting.
Start small. Pick a few test domains. Build the workflow. Prove the model. Then expand systematically. The investment pays off quickly.
The principles we applied to infrastructure management are universally applicable to domains and DNS management:
- Treat infrastructure as code
- Centralize management in version control
- Automate the workflow
- Make changes reviewable and reversible
- Build for compliance from the start
Ready to bring Infrastructure as Code to your domain and DNS management? Give DNSimple a try free for 30 days, and see how you can manage your domains and DNS the same way you manage your application code. And as always, if you have any questions about domain management, DNS configuration, or the Terraform provider, get in touch — we'd love to help.
Happy automating!
Simone Carletti
Italian software developer, a PADI scuba instructor and a former professional sommelier. I make awesome code and troll Anthony for fun and profit.
We think domain management should be easy.
That's why we continue building DNSimple.