Managing DNS with DNSControl
Learn how to manage your DNS zones and records as code using DNSControl with the DNScale provider. Define your entire DNS configuration in JavaScript.
Answer snapshot
DNSControl manages DNS as JavaScript code. You declare zones and records in dnsconfig.js, run dnscontrol preview to diff against the live state, and dnscontrol push to apply. The DNScale provider is upstream; install it, drop your API key into creds.json, and your zones are now version-controlled, code-reviewed, and CI-deployable. Best fit: teams that want shared macros and reusable DNS patterns without Terraform's state file.
What you'll learn
- Set up the DNScale DNSControl provider and manage DNS zones and records through JavaScript configuration files
- Use JavaScript variables, functions, and macros to build reusable DNS patterns across multiple domains
- Implement a CI/CD pipeline that previews DNS changes on pull requests and applies them on merge
- Configure multi-provider DNS setups using DNSControl with DNScale alongside other providers
DNSControl lets you manage DNS with a JavaScript-based configuration file. You define your zones and records in dnsconfig.js, preview changes with dnscontrol preview, and apply them with dnscontrol push. The DNScale provider integrates directly with the DNScale API, giving you full control over your DNS infrastructure as code.
For an alternative IaC approach, see the Terraform Provider Guide.
Prerequisites
Before you begin, ensure you have:
- DNSControl installed (version 4.0 or later) - Install DNSControl
- A DNScale account with an API key - Get your API key
- Basic familiarity with JavaScript syntax
Setup
DNSControl uses a creds.json file for provider credentials. Create this file in your project directory:
{
"dnscale": {
"TYPE": "DNSCALE",
"api_key": "your-api-key-here"
}
}The provider connects to https://api.dnscale.eu/v1 by default. To use a custom API endpoint, add api_url:
{
"dnscale": {
"TYPE": "DNSCALE",
"api_key": "your-api-key-here",
"api_url": "https://custom-api.example.com/v1"
}
}Keep creds.json out of version control. Add it to your .gitignore:
echo "creds.json" >> .gitignoreCreating Your First Zone
Create a dnsconfig.js file with a basic zone definition:
var REG_NONE = NewRegistrar("none");
var DSP_DNSCALE = NewDnsProvider("dnscale");
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", "192.0.2.1"),
A("www", "192.0.2.1")
);This configuration:
- Declares the DNScale provider using credentials from
creds.json - Creates a zone for
example.com - Adds two A records: one at the apex (
@) and one forwww
When you run dnscontrol push, DNScale automatically creates the zone if it doesn't exist yet.
Managing DNS Records
A Records
Point hostnames to IPv4 addresses. See What Is an A Record for details:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", "192.0.2.1"),
A("www", "192.0.2.1"),
A("api", "192.0.2.10", TTL(300))
);AAAA Records
Add IPv6 support:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
AAAA("@", "2001:db8::1"),
AAAA("www", "2001:db8::1")
);CNAME Records
Create aliases for subdomains:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
CNAME("blog", "example.github.io."),
CNAME("docs", "readthedocs.io.")
);ALIAS Records
Use ALIAS for apex-level CNAME-like behavior:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
ALIAS("@", "loadbalancer.example.net.")
);MX Records
Configure email delivery:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
MX("@", 10, "mail.example.com."),
MX("@", 20, "mail2.example.com.")
);TXT Records
Add SPF, DKIM, or verification records:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
TXT("@", "v=spf1 include:_spf.google.com ~all"),
TXT("@", "google-site-verification=abc123"),
TXT("_dmarc", "v=DMARC1; p=reject; rua=mailto:dmarc@example.com")
);CAA Records
Control which certificate authorities can issue certificates for your domain:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
CAA("@", "issue", "letsencrypt.org"),
CAA("@", "issuewild", "letsencrypt.org"),
CAA("@", "iodef", "mailto:security@example.com")
);SRV Records
Define service locations:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
SRV("_sip._tcp", 10, 60, 5060, "sip.example.com.")
);PTR Records
Create reverse DNS entries:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
PTR("1", "server.example.com.")
);SSHFP Records
Publish SSH host key fingerprints:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
SSHFP("@", 1, 1, "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3")
);TLSA Records
Associate TLS certificates with domain names for DANE:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
TLSA("_443._tcp", 3, 1, 1, "abc123def456...")
);HTTPS and SVCB Records
Configure service binding for modern HTTPS connections:
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
HTTPS("@", 1, ".", "alpn=h2,h3"),
SVCB("_dns.resolver", 1, "dns.example.com.", "alpn=dot")
);Advanced Configuration
Multiple Domains
Manage several domains in one configuration file:
var REG_NONE = NewRegistrar("none");
var DSP_DNSCALE = NewDnsProvider("dnscale");
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", "192.0.2.1"),
A("www", "192.0.2.1"),
MX("@", 10, "mail.example.com.")
);
D("example.org", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", "192.0.2.2"),
CNAME("www", "example.org.")
);
D("example.net", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", "192.0.2.3"),
CNAME("www", "example.net.")
);Using Variables
Since dnsconfig.js is JavaScript, you can use variables to reduce repetition:
var REG_NONE = NewRegistrar("none");
var DSP_DNSCALE = NewDnsProvider("dnscale");
var WEBSERVER_IP = "192.0.2.1";
var MAIL_SERVERS = [
MX("@", 10, "mx1.mailprovider.com."),
MX("@", 20, "mx2.mailprovider.com.")
];
var SPF = TXT("@", "v=spf1 include:_spf.mailprovider.com ~all");
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", WEBSERVER_IP),
A("www", WEBSERVER_IP),
MAIL_SERVERS,
SPF
);
D("example.org", REG_NONE, DnsProvider(DSP_DNSCALE),
A("@", WEBSERVER_IP),
A("www", WEBSERVER_IP),
MAIL_SERVERS,
SPF
);Macros with Functions
Create reusable record patterns using JavaScript functions:
function WEBSITE(ip) {
return [
A("@", ip),
A("www", ip),
AAAA("@", "2001:db8::1"),
AAAA("www", "2001:db8::1")
];
}
function GOOGLE_WORKSPACE() {
return [
MX("@", 1, "aspmx.l.google.com."),
MX("@", 5, "alt1.aspmx.l.google.com."),
MX("@", 5, "alt2.aspmx.l.google.com."),
TXT("@", "v=spf1 include:_spf.google.com ~all")
];
}
D("example.com", REG_NONE, DnsProvider(DSP_DNSCALE),
WEBSITE("192.0.2.1"),
GOOGLE_WORKSPACE()
);Multi-Provider DNS
DNSControl natively supports multi-provider DNS deployments. You can define multiple providers and use them together for a single zone, ensuring redundancy and resilience against DNS attacks.
DNScale assigns nameservers automatically when a zone is created, so you need to explicitly declare them with NAMESERVER() for registrar delegation. Third-party NS records at the apex are fully supported — DNSControl will create and manage them on DNScale alongside DNScale's own system-managed nameservers:
var REG_NAMECHEAP = NewRegistrar("namecheap");
var DSP_DNSCALE = NewDnsProvider("dnscale");
var DSP_ROUTE53 = NewDnsProvider("route53");
D("example.com", REG_NAMECHEAP,
DnsProvider(DSP_DNSCALE),
DnsProvider(DSP_ROUTE53),
// DNScale nameservers must be declared explicitly
NAMESERVER("ns1.dnscale.eu"),
NAMESERVER("ns2.dnscale.eu"),
A("@", "192.0.2.1"),
A("www", "192.0.2.1"),
MX("@", 10, "mail.example.com.")
);When you run dnscontrol push, it synchronizes the records to both providers simultaneously. The NAMESERVER() entries ensure that both providers' nameservers are included in registrar delegation. Route53's nameservers are picked up automatically from the provider, while DNScale's need the explicit declaration above.
Preview and Push Workflow
DNSControl uses a two-step workflow: preview your changes first, then apply them.
Preview Changes
dnscontrol previewThis connects to the DNScale API, compares your dnsconfig.js with the current live records, and shows what would change:
******************** Domain: example.com
1 correction (dnscale)
#1: CREATE A www.example.com 192.0.2.1 ttl=3600Apply Changes
Once you're satisfied with the preview, apply the changes:
dnscontrol push******************** Domain: example.com
1 correction (dnscale)
#1: CREATE A www.example.com 192.0.2.1 ttl=3600
Done. 1 correction.Always preview before pushing. DNS changes are global and take time to propagate. A mistake in an MX record could disrupt email delivery; a wrong A record could take your site offline. The preview step is your safety net.
Best Practices
Version Control Your Configuration
Keep dnsconfig.js in Git to track every DNS change:
git init
echo "creds.json" >> .gitignore
git add dnsconfig.js .gitignore
git commit -m "Initial DNS configuration"Always Preview Before Pushing
Run dnscontrol preview before every dnscontrol push. This catches mistakes before they affect live DNS.
Use CI/CD for Automated Deployments
Add DNSControl to your CI/CD pipeline. Run preview on pull requests and push on merge to main:
# .github/workflows/dns.yml
name: DNS
on:
pull_request:
paths: ["dnsconfig.js"]
push:
branches: [main]
paths: ["dnsconfig.js"]
jobs:
dns:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Install DNSControl
run: go install github.com/StackExchange/dnscontrol/v4@latest
- name: Preview
if: github.event_name == 'pull_request'
run: dnscontrol preview
env:
DNSCALE_API_KEY: ${{ secrets.DNSCALE_API_KEY }}
- name: Push
if: github.ref == 'refs/heads/main'
run: dnscontrol push
env:
DNSCALE_API_KEY: ${{ secrets.DNSCALE_API_KEY }}This workflow ensures that every DNS change goes through code review. The preview step on pull requests shows reviewers exactly what will change, and the push step only runs after the PR is approved and merged.
For production environments, consider adding a manual approval step between
previewandpush. A GitHub Environment with required reviewers adds a human gate before DNS changes go live.
Keep Credentials Secure
Never commit creds.json. Use environment variables or a secrets manager in CI/CD. DNSControl supports reading credentials from environment variables:
{
"dnscale": {
"TYPE": "DNSCALE",
"api_key": "$DNSCALE_API_KEY"
}
}Set Appropriate TTLs
Use the TTL() function to set TTL values per record. Records that change frequently (like API endpoints) benefit from shorter TTLs, while stable records (like MX or TXT) can use longer ones:
A("api", "192.0.2.10", TTL(300)), // 5 minutes - may change
MX("@", 10, "mail.example.com.", TTL(86400)) // 24 hours - stableComplete Example
Here's a full dnsconfig.js for a typical website with email:
var REG_NONE = NewRegistrar("none");
var DSP_DNSCALE = NewDnsProvider("dnscale");
var SERVER_IP = "203.0.113.50";
D("mywebsite.com", REG_NONE, DnsProvider(DSP_DNSCALE),
// Website
A("@", SERVER_IP),
AAAA("@", "2001:db8::50"),
CNAME("www", "mywebsite.com."),
// API subdomain
A("api", "203.0.113.51", TTL(300)),
// Email (Google Workspace)
MX("@", 1, "aspmx.l.google.com."),
MX("@", 5, "alt1.aspmx.l.google.com."),
MX("@", 5, "alt2.aspmx.l.google.com."),
MX("@", 10, "alt3.aspmx.l.google.com."),
MX("@", 10, "alt4.aspmx.l.google.com."),
// Email security
TXT("@", "v=spf1 include:_spf.google.com ~all"),
TXT("_dmarc", "v=DMARC1; p=reject; rua=mailto:dmarc@mywebsite.com"),
// Certificate authority authorization
CAA("@", "issue", "letsencrypt.org"),
CAA("@", "issuewild", "letsencrypt.org"),
// HTTPS service binding
HTTPS("@", 1, ".", "alpn=h2,h3")
);Next Steps
- Read the DNSControl documentation for the full configuration 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 the Terraform Provider for an alternative IaC approach
- Learn about DNS security to understand what you're protecting
- Review DNSSEC Key Management for signing your zones
Conclusion
DNSControl brings DNS-as-code to your workflow with a straightforward JavaScript configuration. Combined with the DNScale provider, you get version-controlled DNS changes, safe preview-before-push workflows, and the ability to manage multiple domains from a single file. Whether you're managing one domain or hundreds, DNSControl provides the tooling to do it reliably and at scale.
dnscontrol push your DNS today.
Frequently asked questions
- How is DNSControl different from Terraform for DNS?
- Terraform manages DNS as part of broader infrastructure with a state file; great when DNS lives alongside compute, networking, and IAM. DNSControl is DNS-only, has no state file, and uses pure JavaScript for record definitions — easier macros and reusable patterns, but no integration with non-DNS resources. Pick Terraform if you already use it for everything else; pick DNSControl if DNS is your sole concern or you want a simpler mental model.
- Can DNSControl manage DNScale alongside other providers?
- Yes — DNSControl is multi-provider by design. You list each provider in creds.json and tag each domain with the providers it should publish to. The same dnsconfig.js can push identical records to DNScale, Hetzner DNS, Route 53, Cloudflare, etc. — one of the cleanest ways to run multi-provider DNS.
- Where is the DNScale provider documented?
- In the DNSControl upstream documentation under 'DNScale provider'. The provider supports all standard record types (A, AAAA, MX, CNAME, NS, TXT, SRV, CAA, PTR), DNSSEC operations, and zone-level metadata. The DNScale Learning section also has runnable end-to-end examples — see the dnscontrol-guide page itself.
- Does DNSControl support DNSSEC?
- Yes — at the provider level. You configure DNSSEC enablement on a per-zone basis in dnsconfig.js; the DNScale provider then handles key generation and DS-record reporting through the API. Rotation and re-signing happen on the DNScale side, transparent to your DNSControl runs.
- How do I run dnscontrol in CI/CD?
- Standard pattern: dnscontrol check on every PR (catches syntax errors), dnscontrol preview on PR (posts the diff as a comment), dnscontrol push on merge to main (applies). Store the DNScale API key as a CI secret. The DNSControl docs and the DNScale Learning page both include working GitHub Actions and GitLab CI examples.
Related guides
Automation
Managing DNS with Terraform
Manage DNS zones, records, and DNSSEC as code with the DNScale Terraform provider. Install the provider, create records, use CI/CD, and avoid DNS downtime.
Automation
DNS for Cloud Infrastructure — Best Practices and Architecture
Learn cloud DNS best practices including service discovery, multi-cloud strategies, automation with Terraform, and TTL optimization for dynamic infrastructure.
Automation
DNS as Code Best Practices
A practical guide to managing DNS as code with ownership, review, previews, drift detection, scoped credentials, and safe migration patterns.
Automation
DNS in CI/CD Pipelines
How to manage DNS changes through CI/CD with checks, previews, approvals, scoped secrets, drift detection, and safe rollback.
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