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.
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
- name: Install DNSControl
run: |
curl -sL https://github.com/StackExchange/dnscontrol/releases/latest/download/dnscontrol-Linux -o dnscontrol
chmod +x dnscontrol
- 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.
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