Need email infrastructure? Try PostScale -- transactional email API built in the EU. PostScale

    AutomationIntermediate

    GitLab CI DNS Workflows

    Practical GitLab CI patterns for DNSControl and Terraform DNS changes, including merge-request previews, protected deploys, scoped variables, and drift checks.

    Answer snapshot

    A safe GitLab CI DNS workflow validates and previews DNS changes in merge requests, then applies only from a protected branch or protected environment. Store DNScale credentials as protected and masked CI/CD variables, scope production variables to production jobs, and run scheduled drift checks so manual dashboard changes are detected instead of silently overwritten.

    What you'll learn

    • Build GitLab merge-request previews for DNSControl and Terraform
    • Apply DNS changes only from protected branches and environments
    • Store DNScale API keys using GitLab CI/CD variable controls
    • Add scheduled drift checks and rollback paths

    GitLab CI can run DNS changes safely when the pipeline has a hard boundary:

    merge request = validate and preview
    protected branch or environment = apply

    This guide complements DNS in CI/CD Pipelines, GitHub Actions DNS Workflows, and DNS Automation.

    For GitLab syntax details, keep the official GitLab CI/CD YAML reference, CI/CD variables documentation, and protected environments documentation nearby.

    Repository Layout

    Keep DNS easy to review.

    dns/
      dnsconfig.js
      creds.example.json
      zones/
        example.com.js
     
    terraform/
      dns/
        main.tf
        variables.tf
        versions.tf

    Pick one source of truth per zone. Mixing Terraform, DNSControl, dashboard edits, and ad hoc scripts for the same record set creates drift.

    GitLab Variables

    Store DNScale credentials as GitLab CI/CD variables, not in .gitlab-ci.yml.

    Recommended variables:

    VariableUse
    DNSCALE_READ_TOKENPreview, plan, and drift checks where read-only access is enough
    DNSCALE_PRODUCTION_TOKENApply jobs for production DNS
    DNSCALE_STAGING_TOKENApply jobs for non-production DNS

    For production variables:

    • mark them protected
    • mark them masked if the value format allows it
    • scope them to the production environment where possible
    • avoid making them available to merge-request pipelines from untrusted sources

    DNScale tokens should be zone-scoped where possible. A token for example.com should not be able to edit every zone in the account.

    Pipeline Shape

    Use stages:

    stages:
      - validate
      - preview
      - apply
      - drift

    Merge requests run validate and preview. The default branch runs apply. A schedule runs drift.

    DNSControl Merge-Request Preview

    This job checks DNSControl config and prints a preview in merge-request pipelines.

    dnscontrol:preview:
      stage: preview
      image: golang:1.25
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - dns/**/*
            - .gitlab-ci.yml
      before_script:
        - go install github.com/StackExchange/dnscontrol/v4@latest
      script:
        - cd dns
        - dnscontrol check
        - DNSCALE_API_KEY="$DNSCALE_READ_TOKEN" dnscontrol preview

    If the provider requires write-capable credentials for preview, do not run this job in untrusted merge-request contexts.

    DNSControl Apply

    Apply only from the default branch and attach the job to a protected environment.

    dnscontrol:apply:
      stage: apply
      image: golang:1.25
      environment:
        name: production
        deployment_tier: production
      rules:
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - dns/**/*
            - .gitlab-ci.yml
      before_script:
        - go install github.com/StackExchange/dnscontrol/v4@latest
      script:
        - cd dns
        - dnscontrol check
        - DNSCALE_API_KEY="$DNSCALE_PRODUCTION_TOKEN" dnscontrol push

    Protect the production environment in GitLab so only approved maintainers or release roles can deploy.

    Terraform Merge-Request Plan

    For Terraform-managed DNS:

    terraform:dns:plan:
      stage: preview
      image: hashicorp/terraform:1.9
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - terraform/dns/**/*
            - .gitlab-ci.yml
      variables:
        TF_IN_AUTOMATION: "true"
      script:
        - cd terraform/dns
        - terraform fmt -check
        - terraform init -input=false
        - terraform validate
        - DNSCALE_API_KEY="$DNSCALE_READ_TOKEN" terraform plan -input=false

    If your Terraform state backend needs production credentials, decide whether merge-request plans are allowed to access that backend. If not, use a separate read-only plan path or restrict plans to trusted branches.

    Terraform Apply

    terraform:dns:apply:
      stage: apply
      image: hashicorp/terraform:1.9
      environment:
        name: production
        deployment_tier: production
      rules:
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - terraform/dns/**/*
            - .gitlab-ci.yml
      variables:
        TF_IN_AUTOMATION: "true"
      script:
        - cd terraform/dns
        - terraform init -input=false
        - DNSCALE_API_KEY="$DNSCALE_PRODUCTION_TOKEN" terraform apply -input=false -auto-approve

    For high-risk zones, avoid fully automatic apply. Use protected environments, required approvals, or a manual job:

      when: manual
      allow_failure: false

    Scheduled Drift Checks

    GitLab schedules can run a non-mutating drift check.

    DNSControl:

    dnscontrol:drift:
      stage: drift
      image: golang:1.25
      rules:
        - if: '$CI_PIPELINE_SOURCE == "schedule"'
      before_script:
        - go install github.com/StackExchange/dnscontrol/v4@latest
      script:
        - cd dns
        - DNSCALE_API_KEY="$DNSCALE_READ_TOKEN" dnscontrol preview

    Terraform:

    terraform:dns:drift:
      stage: drift
      image: hashicorp/terraform:1.9
      rules:
        - if: '$CI_PIPELINE_SOURCE == "schedule"'
      variables:
        TF_IN_AUTOMATION: "true"
      script:
        - cd terraform/dns
        - terraform init -input=false
        - DNSCALE_API_KEY="$DNSCALE_READ_TOKEN" terraform plan -detailed-exitcode -input=false

    With terraform plan -detailed-exitcode, exit code 2 means changes are present. Treat that as a drift signal that needs review.

    Rollback

    Rollback should use the same path as the original change:

    git revert <bad-commit>
    git push

    Then let the pipeline validate, preview, apply, and verify. For migrations, prepare the rollback commit before the change window begins.

    Guardrails

    Use these rules for production DNS:

    • Merge requests preview DNS changes but do not mutate production.
    • Production apply jobs run only from protected branches or protected environments.
    • DNScale production tokens are protected, masked, and environment-scoped.
    • Each token is scoped to the minimum set of zones.
    • Drift jobs alert; they do not automatically overwrite manual changes.
    • DS, DNSKEY, NS, MX, CAA, and apex changes require extra review.

    Frequently asked questions

    Should GitLab CI apply DNS changes from merge requests?
    No. Merge requests should validate and preview. Apply jobs should run only after merge to a protected branch or through a protected production environment.
    Where should the DNScale API key live in GitLab?
    Store it as a GitLab CI/CD variable. Use protected and masked settings for production tokens, and scope variables to the environments that need them.
    Can I use the same pipeline for Terraform and DNSControl?
    Yes, but keep each tool's state and source of truth separate. Terraform is better when DNS is tied to infrastructure state. DNSControl is better for DNS-first, multi-provider, and zone-oriented workflows.
    How do I prevent forked or untrusted pipelines from seeing DNS secrets?
    Do not expose production variables to untrusted pipeline contexts. Use protected variables, protected branches/tags, environment-scoped variables, and manual approvals for production jobs.
    What should a DNS drift job do?
    Run a non-mutating preview or plan on a schedule and alert if live DNS differs from code. Do not automatically overwrite drift without review.
    How should rollback work?
    Rollback should be a normal revert or follow-up commit that restores previous DNS state through the same validate, preview, apply, and verify path.

    Related guides

    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