Skip to content

Manage DTM as code

Your DNS changes go through the web UI today; this tutorial moves them into pull requests. By the end, the provider is installed from the cluster’s own mirror, a service account (not a person) applies changes from CI, your existing zone is Terraform-owned without having been recreated, and you have rehearsed a token rotation before you need one under pressure.

One disambiguation up front: this covers the DTM Terraform provider, which manages DNS objects on a running cluster. Deploying the cluster infrastructure itself with Terraform is a different surface; see Deploy a cluster with Terraform.

The provider ships inside the DTM image, at the same version as the server running beside it, and your cluster serves it to terraform init over HTTPS. There is no public registry and no separate download. Generate the CLI configuration from any node:

Terminal window
dtm-server --print-terraformrc

Copy the printed provider_installation block into your Terraform CLI configuration file, then run terraform init. Two things have to be true first: the provider mirror is off by default, so an admin turns it on, and the API needs a certificate your workstation can verify. Both are in provider setup.

Export DTM_ENDPOINT and DTM_TOKEN (an admin token for this bootstrap session only; the service-account resources in the next step are admin-only), with an empty provider "dtm" {} block. Then prove connectivity with a data source, because a plan of an empty configuration never calls the API and proves nothing:

data "dtm_cluster_info" "this" {}
output "alive_nodes" { value = data.dtm_cluster_info.this.alive_count }

Verify: terraform plan prints your live node count. Auth, TLS, and endpoint are all proven in one output.

3. Mint the CI identity, token never in state

Section titled “3. Mint the CI identity, token never in state”

Create the automation identity in Terraform itself, using the ephemeral token resource and a write-only Key Vault sink; the full pattern and its semantics live in service accounts for CI:

variable "token_version" {
type = number
default = 1
}
resource "dtm_service_account" "ci" {
name = "terraform-ci"
role = "editor"
rotation = tostring(var.token_version)
}
ephemeral "dtm_service_account_token" "ci" {
service_account_id = dtm_service_account.ci.id
version = var.token_version
}
resource "azurerm_key_vault_secret" "dtm_token" {
name = "dtm-ci-token"
key_vault_id = var.kv_id
value_wo = ephemeral.dtm_service_account_token.ci.token
value_wo_version = var.token_version
}

Two semantics worth internalizing now: every service-account attribute forces replacement (a changed credential is always a fresh credential, by design), and bumping only the ephemeral version against an unchanged account mints an empty token at apply, which is why the three versions above always move together. If the CI pipeline should only ever touch this one zone, add zone_ids = [dtm_zone.example.id] to the account to confine its writes to that zone; scope narrows the role, never widens it, and changing it forces replacement like any other attribute (see zone scoping).

Verify the whole point of the pattern:

Terminal window
grep -c dtmsat_ terraform.tfstate
# 0

4. Adopt the existing zone; do not recreate it

Section titled “4. Adopt the existing zone; do not recreate it”

Bring the UI-created zone under Terraform with terraform import, never by declaring and re-applying (which would collide with the live objects). The dtm_zone data source resolves the zone ID by name; get the record ID from the API, then import with the documented ID forms (<zone-id> and <zone-id>:<record-id>, per zones and records in Terraform):

Terminal window
curl --cacert corp-ca.pem \
https://dtm.internal:8443/api/v1/zones/<zone-id>/records \
-H "Authorization: Bearer $DTM_TOKEN" # note the web record's id
terraform import dtm_zone.example <zone-id>
terraform import dtm_record.web "<zone-id>:<record-id>"

Set protected = true on the adopted record so a stray bulk delete cannot remove IaC-owned records.

Verify: terraform plan is clean (no changes), which is the proof the imported state matches reality.

Add a new health-checked, load-balanced record entirely in HCL (the health and load-balancing page covers every attribute):

resource "dtm_record" "api" {
zone_id = dtm_zone.example.id
name = "api"
type = "A"
ttl = 30
values {
value = "10.0.1.20"
}
values {
value = "10.0.1.21"
}
lb_policy { method = "failover" }
health_check_template {
protocol = "tcp"
port = 443
interval_seconds = 5
}
}

Verify with the same discipline as any change:

Terminal window
dig @10.0.250.4 api.example.internal +short
curl --cacert corp-ca.pem https://dtm.internal:8443/api/v1/health/status \
-H "Authorization: Bearer $DTM_TOKEN"

Any CI system works; the shape is identical: on pull request, read the dtm-ci-token secret from Key Vault, export it as DTM_TOKEN, run terraform plan and post it for review; on merge, terraform apply. GitHub Actions sketch:

- name: Fetch DTM token
run: echo "DTM_TOKEN=$(az keyvault secret show --vault-name kv-corp \
--name dtm-ci-token --query value -o tsv)" >> "$GITHUB_ENV"
- run: terraform plan -input=false # apply on the merge branch instead

Scope note: Terraform manages your DNS objects and runtime config; it does not manage backups or the deploy-time bootstrap secrets (the initial administrator password, the cluster encryption key, and the first-boot TLS material). Those stay with their own procedures.

Rotate the CI token now, while nothing is on fire, with exactly one move: bump var.token_version and apply. That single bump drives the account replacement, the fresh token mint, and the Key Vault re-write together; the account’s ID changes, which is expected.

Verify: the next pipeline run authenticates with the new token.

Close the loop on step 2’s admin token: login tokens expire after 24 hours and have no revoke endpoint, so retirement means ceasing use and letting it expire; if you created a dedicated bootstrap user, disable it per users and access. Then confirm the audit trail tells the story you want auditors to read:

Terminal window
curl --cacert corp-ca.pem \
"https://dtm.internal:8443/api/v1/audit?user_name=terraform-ci&limit=50" \
-H "Authorization: Bearer $DTM_TOKEN"

Changes from here on are attributed to terraform-ci, driven by reviewed merges, with a clean terraform plan as your steady state.

Need a hand? Email [email protected].

Last validated: 2026-07-26