Managing your DNS records with Terraform

At my first FOSDEM, I went together with a co-worker to see a talk from Matteo Valentini regarding DNS and how to manage your records with a CI/CD pipeline.

He showed us octoDNS a python tool from GitHub able to sync your local configuration with your DNS records managed at any thinkable cloud provider.

Until last weekend I was still using octoDNS to automatically manage my DNS on Azure through a CI/CD pipeline run with drone.

But I decided to switch to a different solution consisting of terraform and Digitalocean while keeping the pipeline on a self hosted drone server.

Setting up your project

I created a file and a separate file for every single DNS zone I want to manage:


The contents of will describe the provider we want to use (in this case digitalocean/digitalocean), our API Token as variable and the remote backend s3 which will be a space/bucket on Digitalocean. We will use the backend to save our terraform.tfstate.

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "2.0.1"

  # DigitalOcean uses the S3 spec.
  backend "s3" {
    bucket = "mybucketname"
    # filename to use for saving our tfstate
    key    = "terraform.tfstate" 
    # depends where you are setting up the space (fra1/ams1 etc..)
    endpoint = "" 
    # DO uses the S3 format
    # eu-west-1 is used to pass TF validation
    region = "eu-west-1" 
    # Deactivate a few checks as TF will attempt these against AWS
    skip_credentials_validation = true
    skip_metadata_api_check = true

# our digitalocean api token
variable "do_token" {} 

provider "digitalocean" {
  token = var.do_token 

The domain zone file

Our domain zone file will be kept very simple:

resource "digitalocean_domain" "examplecom" {
   name = ""
   ip_address = "" # default @ record

resource "digitalocean_record" "examplecom-mail" {
  domain =
  type = "A"
  name = "mail"
  value = "" # resolves to this IP

resource "digitalocean_record" "examplecom-mx" {
  domain =
  type = "MX"
  name = "@"
  priority = 10
  value = "" # MX record

resource "digitalocean_record" "examplecom-www" {
  domain =
  type = "CNAME"
  name = "www"
  value = "@" # CNAME record >

resource "digitalocean_record" "examplecom-txt-keybase" {
  domain =
  type = "TXT"
  name = "_keybase"
  value = "keybase-site-verification=SECRETCODE" # keybase verification TXT record

resource "digitalocean_record" "examplecom-srv-imap-tcp" {
  domain =
  type = "SRV"
  name = "_imap._tcp"
  value = "" # SRV record for imap
  port = "143"
  priority = 0
  weight = 1


What we now need is a init, plan & apply to finish this up. But first we will have to export our secrets

export TF_VAR_do_token=SECRET_API_TOKEN
# has nothing to do with AWS, it's still Digitalocean, but terraform's s3 backend reads this

terraform init
Initializing the backend...

Initializing provider plugins...
- Using previously-installed digitalocean/digitalocean v2.0.1

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

terraform plan
digitalocean_record.examplecom-www: Refreshing state... 
digitalocean_record.examplecom-mail: Refreshing state...
digitalocean_record.examplecom-mx: Refreshing state... 
Plan: 6 to add, 0 to change, 0 to destroy.

terraform apply # confirm with yes

After applying the changes, please check that your terraform.tfstate has been uploaded to the Digitalocean space and check if the DNS is actually working:

host has address

Automating it

Let’s automate this by running a pipeline with drone. You can of course use any other CI/CD pipeline tooling you want to. For the main step in the pipeline we’ll be using the hashicorp/terraform container image.

Example .drone.yml:

kind: pipeline
type: docker
name: dns

  - name: terraform
    image: hashicorp/terraform:0.13.4
      - terraform init
      - terraform plan
      - terraform apply -auto-approve
    # keep your secrets secret and not inside GIT!
        from_secret: tf_var_do_token
        from_secret: aws_secret_access_key
        from_secret: aws_access_key_id
      branch: master

This way any time you push a new change to your master branch, the pipeline will take care of the rest.

And thanks to the remote backend being configured, you’ll be able to also apply your changes manually, from any device.