DNS is one of those services that still requires logging into some web portal and clicking buttons and entering data. Heaven forbid you enter a bad IP address and take down your service – there’s no “rollback to previous value” option. Want to track your configurations over time? Sorry, not something your web portal can often do. DNS configuration should be simple, trackable, and automatable.
Good news! You can take charge of your DNS configuration with Git, GitHub and OctoDNS.
OctoDNS is a library for managing your DNS, which ships with a series of commands for syncing, dumping, and reporting. OctoDNS was originally developed at GitHub to aid in tracking DNS across our domains and in helping integrate DNS management into our normal tools, like Git, our deployment stack, and so on.
OctoDNS operates kind of like rsync
: you specify a source (usually YAML files)
and specify a destination or destinations (DNS providers) and ask it to sync.
Any configurations present in the source configuration (YAML) will sync to the
destination (DNS providers). I haven’t tried, but theoretically you can even
sync from one DNS provider to another and skip YAML altogether! But the point of
this blog post is to share how we manage DNS with Git & GitHub so we’ll be using
YAML.
We’ll create our repository and dump a zone to get started. Create a repository on GitHub, initialize with a readme, and clone:
~$ git clone https://github.com/YOURUSERNAME/dns
~$ cd dns
Now, make a config
directory and specify the source & destination for your
configurations. At GitHub we name this file after its environment. We’ll be
fancy and call our environment “production.”
In this example, I’ll use Cloudflare as my DNS provider but you can use any other
supported providers. The records they support depend on their API capabilities,
so make sure to check that your provider supports creating the types of records you want.
In this file, we’ll use the built-in environment variable expansion. This means
any octodns-*
commands will need to be run with these variables set.
~/dns$ mkdir config
~/dns$ cat > config/production.yaml <<EOF
---
providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: ./config
cloudflare:
class: octodns.provider.cloudflare.CloudflareProvider
email: env/CLOUDFLARE_EMAIL
token: env/CLOUDFLARE_TOKEN
zones:
example.com.:
sources:
- config
targets:
- cloudflare
EOF
Ok, so the configuration specifies two things: the providers, and the zones.
The providers are your sources and destinations for the DNS records. Here, we’re
specifying a provider called config
which reads the YAML from ./config
. We
also specify a provider called cloudflare
which uses Cloudflare’s API and
requires the CLOUDFLARE_EMAIL
and CLOUDFLARE_TOKEN
environment variables.
The zones are your domains! You can specify unique sources and targets for each.
“Target” is our destination, and you can specify as many as you wish! They must
be configured in the providers
dictionary to use them.
Now that you have your configuration file in place, you can run OctoDNS! First, we’ll dump the zone you added to your configuration file. OctoDNS will create a new YAML file with all the records it could find in the target you specify.
We’ll use Docker to make installation a little simpler, but if you’re into managing your own Python virtualenv and such, feel free to do that. The OctoDNS repository’s readme has great documentation on running it straight on your host.
$ docker run -it --rm \
-e CLOUDFLARE_EMAIL=yourlogin@gmail.com \
-e CLOUDFLARE_TOKEN=yourtoken \
-v $(pwd):/opt/dns \
parkr/octodns:v0.9.4 \
octodns-dump \
--config-file /opt/dns/config/production.yaml \
--output-dir /opt/dns/config \
"example.com." \
cloudflare
This will print out a YAML file in config/example.com.yaml
, containing your
DNS entries (comments my own):
---
# These are the top-level DNS records for example.com.
# This specifies:
# 1. ALIAS example.com to subdomainarecords.example.com
# 2. MX example.com to mxa.emailprovider.com at priority 10 and mxb.emailprovider.com at priority 10
# 3. TXT example.com with "v=spf1..."
? ''
: - ttl: 300
type: ALIAS
value: subdomainarecords.example.com.
- ttl: 300
type: MX
values:
- exchange: mxa.emailprovider.com.
preference: 10
- exchange: mxb.emailprovider.com.
preference: 10
- ttl: 300
type: TXT
value: v=spf1 include:emailprovider.com ~all
# Here is an example of a CNAME record, subdomaincname.example.com.
# It points back to example.com.
subdomaincname:
ttl: 300
type: CNAME
value: example.com.
# And here is an example of an A record with corresponding IPv6 AAAA record,
# subdomainarecords.example.com.
subdomainarecords:
- ttl: 300
type: A
value: 100.100.100.100
- ttl: 300
type: AAAA
value: 2001:2001:2001:2001:2001:2001:2001:2001
Whew! This is great! Now we can commit this file and propose a change.
Let’s make a change. Let’s modify the A records for subdomainarecords.example.com. To do this, we’ll update the YAML:
subdomainarecords:
- ttl: 300
type: A
value: 300.300.300.300
- ttl: 300
type: AAAA
value: 4001:4001:4001:4001:4001:4001:4001:4001
Now we’ll test our changes with octodns-sync
:
$ docker run -it --rm \
-e CLOUDFLARE_EMAIL=yourlogin@gmail.com \
-e CLOUDFLARE_TOKEN=yourtoken \
-v $(pwd):/opt/dns \
parkr/octodns:v0.9.4 \
octodns-sync --config-file /opt/dns/config/production.yaml
It will print out some beautiful output showing the before and after picture (it will NOT apply the changes yet):
********************************************************************************
* example.com.
********************************************************************************
* cloudflare (CloudflareProvider)
* Update
* <AaaaRecord AAAA 300, subdomainarecords.example.com., ['2001:2001:2001:2001:2001:2001:2001:2001']> ->
* <AaaaRecord AAAA 300, subdomainarecords.example.com., ['4001:4001:4001:4001:4001:4001:4001:4001']> (config)
* Update
* <ARecord A 300, subdomainarecords.example.com., ['100.100.100.100']> ->
* <ARecord A 300, subdomainarecords.example.com., ['300.300.300.300']> (config)
* Summary: Creates=0, Updates=2, Deletes=0, Existing Records=6
********************************************************************************
Now, let’s apply our changes! To do this, add the --doit
flag to our octodns-sync
command:
$ docker run -it --rm \
-e CLOUDFLARE_EMAIL=yourlogin@gmail.com \
-e CLOUDFLARE_TOKEN=yourtoken \
-v $(pwd):/opt/dns \
parkr/octodns:v0.9.4 \
octodns-sync --config-file /opt/dns/config/production.yaml --doit
You’ll see CloudflareProvider[cloudflare] apply: making changes
when it’s making your changes.
Tada! Wait for the records to expire (300 seconds) and dig
for your new records!
Now, let’s propose a change using a PR workflow. This is especially important when you’re managing the DNS
--doit
and leave a comment with the output to show the changes you’re making--doit
to apply the changesGitHub Flow helps make managing your DNS much easier.
Want to see it in action? I am managing my own DNS at parkr/dns, as well as the DNS for Jekyll at jekyll/dns.
I hope this helps illustrate how OctoDNS can take the pain away from managing DNS manually in a web portal.