Skip to main content

Command Palette

Search for a command to run...

DNSOps - GitOps for DNS management with DNSControl & GitLab CI/CD

Updated
4 min read
DNSOps - GitOps for DNS management with DNSControl & GitLab CI/CD

This repository provides a GitOps approach to maanage your DNS records live in Git, changes are peer-reviewed, and deployments are automated through CI/CD. When the dashboard is down, your DNS config is still version-controlled and ready to push to a backup provider.

GitOps Pattern: All DNS changes MUST be made through Git commits.Never edit DNS records directly in the provider's dashboard - changes will be overwritten on the next pipeline run.

Why GitOps for DNS?

If your DNS is only managed through a web dashboard, you're one outage away from losing control.

Managing DNS records manually through web dashboards creates several challenges:

ChallengeGitOps Solution
No Audit TrailFull Git history - who changed what, when
No Granular AccessBranch protection, MR approvals, CODEOWNERS
Provider OutagesConfig is versioned, push to backup provider
Manual ErrorsAutomated validation before applying
InconsistencyShared configs, reusable variables

Quick Start

Check out nh4ttruong/dnsops or follow the steps outlined below:

1. Clone and Configure

git clone https://github.com/nh4ttruong/dnsops
cd dnsops

2. Set CI/CD Variables

ProviderVariables
CloudflareCF_ACCOUNT_ID_*, CF_API_TOKEN_*
AWS Route 53AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
Google CloudGCLOUD_PRIVATE_KEY, GCLOUD_CLIENT_EMAIL

3. Edit DNS Records

nano zones/{domain}/dnsconfig.js

4. Commit and Push

git add . && git commit -m "Update DNS records"
git push origin main

Pipeline automatically: checkpreviewpush


Repository Structure

dns-mgmt/
├── .gitlab-ci.yml              # Pipeline triggers per zone
├── creds.json                  # Provider credentials (env vars)
└── zones/
    ├── .gitlab-ci-template.yml # Shared pipeline template
    ├── common.js               # Shared IPs & defaults
    ├── example-com/
    │   └── dnsconfig.js        # Zone: example.com
    └── mycompany-io/
        └── dnsconfig.js        # Zone: mycompany.io

Shared Configuration

// zones/common.js
var addr = {
    "default": IP("10.0.0.10"),
    "private_ingress_uat": IP("10.0.0.70"),
    "public_ingress_uat": IP("203.0.113.19"),
    "public_ingress_production": IP("203.0.113.21"),
}

CI/CD Pipeline

StageJobDescription
testcheck_and_reviewValidate syntax, preview changes
deploypushApply to provider (main branch only)

Provider Examples

Cloudflare

// zones/example-com/dnsconfig.js
require("../common.js");
var DP = NewDnsProvider("cloudflare_example_com");

D("example.com", REG_NONE, DnsProvider(DP), {no_ns: 'true'},
  A("@", addr.default),
  A("www", addr.public_ingress_production),
  CNAME("blog", "example.github.io."),
);
"cloudflare_example_com": {
    "TYPE": "CLOUDFLAREAPI",
    "accountid": "$CF_ACCOUNT_ID_EXAMPLE_COM",
    "apitoken": "$CF_API_TOKEN_EXAMPLE_COM"
}

AWS Route 53

// zones/example-org/dnsconfig.js
require("../common.js");
var DP = NewDnsProvider("route53_example_org");

D("example.org", REG_NONE, DnsProvider(DP),
  A("@", addr.default),
  A("api", addr.public_ingress_production),
  CNAME("www", "example.org."),
);
"route53_example_org": {
    "TYPE": "ROUTE53",
    "KeyId": "$AWS_ACCESS_KEY_ID",
    "SecretKey": "$AWS_SECRET_ACCESS_KEY"
}

Google Cloud DNS

// zones/example-io/dnsconfig.js
require("../common.js");
var DP = NewDnsProvider("gcloud_example_io");

D("example.io", REG_NONE, DnsProvider(DP),
  A("@", addr.default),
  AAAA("@", "2001:db8::1"),
);
"gcloud_example_io": {
    "TYPE": "GCLOUD",
    "project": "my-gcp-project-id",
    "private_key": "$GCLOUD_PRIVATE_KEY",
    "client_email": "$GCLOUD_CLIENT_EMAIL"
}

Adding a New Zone

  1. Create zone directory

     mkdir zones/new-domain-com
    
  2. Create dnsconfig.js

     require("../common.js");
     var DP = NewDnsProvider("cloudflare_new_domain_com");
    
     D("new-domain.com", REG_NONE, DnsProvider(DP), {no_ns: 'true'},
       A("@", addr.default),
     );
    
  3. Add credentials to creds.json

     "cloudflare_new_domain_com": {
         "TYPE": "CLOUDFLAREAPI",
         "accountid": "$CF_ACCOUNT_ID_NEW_DOMAIN_COM",
         "apitoken": "$CF_API_TOKEN_NEW_DOMAIN_COM"
     }
    
  4. Add trigger to .gitlab-ci.yml

     new-domain-com:
       extends: .trigger-base
       variables:
         ZONE_DIR: new-domain-com
       rules:
         - changes: *common-paths
           when: manual
         - changes:
             - zones/new-domain-com/**/*
           when: always
         - when: never
    
  5. Set GitLab CI/CD variables

    • CF_ACCOUNT_ID_NEW_DOMAIN_COM

    • CF_API_TOKEN_NEW_DOMAIN_COM


Local Development

cd zones/{domain}

dnscontrol check                              # Validate syntax
dnscontrol preview --creds ../../creds.json   # Preview changes
dnscontrol push --creds ../../creds.json      # Apply changes

Supported Providers

DNSControl supports 40+ providers:

ProviderTypeBest For
CloudflareCDN + DNSProxy, WAF, DDoS protection
AWS Route 53Cloud DNSAWS ecosystem integration
Google Cloud DNSCloud DNSGCP workloads
Azure DNSCloud DNSAzure ecosystem
DigitalOceanCloud DNSSimple, affordable
NamecheapRegistrarDomain + DNS bundled

Full list: docs.dnscontrol.org/provider


Multi-Provider Strategy

Manage multiple providers in one repository:

  • Migration - Move zones between providers gradually

  • Redundancy - Backup zones on secondary providers

  • Cost optimization - Different providers for different needs


References