Skip to content
Geek and I
Go back

Cloudflare + Terraform DNS Records (Updated: 20191110)

Updated:
Mike Horwath3 min read

I use Cloudflare as a CDN everywhere I can via multiple domains for the different obsessions of mine.

The Cloudflare web-based GUI is intuitive and very easy to manage though I really hate manual entry of things, web-based or not. I like automation.

I also love Terraform.

Cloudflare and Terraform together make life so much easier.

This module can maintain Cloudflare DNS records and is usable with the following DNS types:

Later versions of this module will also allow for LOC records.

Here’s a very basic example of the module to create A and AAAA records through Cloudflare’s network:

module "cloudflare_dns_records" {
source = "git::https://gitlab.com/geekandi-terraform/cloudflare-dns-record-module.git"
domain = "example.com"
# name, value, priority (integer), type, proxied
multi_records = [
["www" , "192.0.2.1", 0, "A", "true"],
["example.com", "192.0.2.1", 0, "A", "true"],
["www" , "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"],
["example.com", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"],
]
}

That looks easy. It is not very maintainable.

Let’s do this again but this time let’s use a vars file to allow for easier maintenance!

Let’s start with my example.com.tfvars file:

domain = "example.com"
# name, value, priority (integer), type, proxied
multi_records = [
["www" , "192.0.2.1", 0, "A", "true"],
["example.com", "192.0.2.1", 0, "A", "true"],
["www" , "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"],
["example.com", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"],
["example.com", "mail.example.com", 10, "MX", "false"],
["example.com", "mail2.example.com", 20, "MX", "false"],
]
# item, proto, priority (integer), weight (integer), port (integer), target (no trailing dot)
srv_records = [
["_sip", "_tls", 1, 100, 443, "sipdir.online.lync.com"],
["_sipfederationtls", "_tcp", 1, 100, 5061, "sipfed.online.lync.com"],
]
# name, flags, tag, value
caa_records = [
["www", "0", "issue", "comodoca.com"],
["www", "0", "issue", "digicert.com"],
["www", "0", "issue", "globalsign.com"],
["example.com", "0", "issue", "comodoca.com"],
["example.com", "0", "issue", "digicert.com"],
["example.com", "0", "issue", "globalsign.com"],
]

And our new Terraform file:

module "cloudflare_dns_records" {
source = "git::https://gitlab.com/geekandi-terraform/cloudflare-dns-record-module.git"
domain = var.domain
zone_id = data.cloudflare_zones.active.zones[0].id
multi_records = var.multi_records
srv_records = var.srv_records
caa_records = var.caa_records
}

Why is this easier? Because now, with a bit of effort on state storage, you can support each domain in a consistent fashion within Cloudflare by using Terraform and multiple vars files. I would suggest looking into using Terraform Workspaces with durable storage via S3.

Using a workspace set up you can do fun things using a S3 backend for the state file yet continue to keep good separation between your different domains.

Here’s a quick example script that forces the issue and I even left something for you, the reader, to finish.

#!/bin/sh
OPERATION=$1
WORKSPACE=$2
if [ -z "${WORKSPACE}" ] ; then
echo "workspace MUST be passed as the 2nd argument"
exit 77
fi
if [ -f vars/${WORKSPACE}.vars ] ; then
echo "we are going to use ${WORKSPACE}.vars file for input"
else
echo "vars file: vars/${WORKSPACE}.vars does not exist"
exit 78
fi
case ${OPERATION} in
plan)
echo "delivering a plan.."
echo ""
terraform workspace select ${WORKSPACE}
if [ $? -eq 1 ] ; then
terraform workspace new ${WORKSPACE}
terraform workspace select ${WORKSPACE}
fi
terraform plan -var-file vars/${WORKSPACE}.vars
terraform workspace select default
break
;;
apply)
echo "applying a change.."
echo ""
terraform workspace select ${WORKSPACE}
if [ $? -eq 1 ] ; then
echo "no workspace available, did you plan first?"
fi
terraform apply -var-file vars/${WORKSPACE}.vars
terraform workspace select default
break
;;
destroy)
echo "operating on a destroy"
break
;;
*)
echo "are you daft?"
exit 78
;;
esac

EDIT 20190306: Converted reusable module to Terraform 0.12 EDIT 20191110: Cloudflare updated their provider and things broke along the way so this has been updated to support the new requirements. You will need to make a data call to get the resulting zone_id that is now required. I have updated the example code below that uses the module and the repository below has an updated README.

I have written a couple of modules but for this post I am only referring to Cloudflare DNS Record Module.

I created a data.tf file to do this for me within my terraform directory:

module data "cloudflare_zones" "active" {
filter {
name = var.domain
}
}


Related Posts

Previous Post
Tesla Model 3: Why?
Next Post
Terraform and AWS instance auto-recovery