Skip to content

Zones and records with Terraform

The dtm_zone and dtm_record resources are the core of managing DNS as code. See zones and records for the concepts.

resource "dtm_zone" "example" {
name = "example.internal."
default_ttl = 300
}

dtm_zone (role: editor or admin) creates an authoritative zone. Give the name as an FQDN with a trailing dot. DTM synthesizes the SOA and NS records; you only set the SOA fields below if you need specific values.

Attribute Required Description
name Yes Zone FQDN with trailing dot, e.g. example.internal.. Changing it replaces the zone.
default_ttl Yes Default TTL (seconds) for records that do not set their own.
lb_policy (block) No Zone-wide default LB policy applied at resolve time to records without their own; same block as dtm_record.lb_policy (see health and LB). Removing the block clears the default.
maintain_reverse No Opt in to automatic reverse (PTR) maintenance for the zone. Defaults to false.
deletion_lock No Server-side deletion lock. Defaults to false: the provider always sends an explicit value, so a Terraform-managed zone is exactly what its config says, even under a locked cluster default (which applies to UI/API creates that omit the field).
primary_ns No SOA primary nameserver. Defaults to ns1.<zone>.
admin_email No SOA contact in DNS form (dots, no @). Defaults to hostmaster.<zone>.
refresh / retry / expire / min_ttl No SOA timers (seconds).
id / serial / status (computed) Server-assigned zone ID, SOA serial, and status (pending / active / deleting).

Use the dtm_zone data source to reference a zone you did not create in this configuration:

data "dtm_zone" "corp" {
name = "corp.example."
}
resource "dtm_record" "api" {
zone_id = data.dtm_zone.corp.id
name = "api"
type = "A"
values { value = "10.0.2.10" }
}

The data-source lookup is forgiving about form: matching is case-insensitive and the trailing dot is optional, so corp.example, Corp.Example., and corp.example. all resolve the same zone. The name you write is kept verbatim in state, so a non-canonical spelling never shows up as drift.

resource "dtm_record" "web" {
zone_id = dtm_zone.example.id
name = "web"
type = "A"
ttl = 60
values { value = "10.0.1.10" }
values { value = "10.0.1.11" }
}

dtm_record (role: editor or admin) manages a record in a zone.

Attribute Required Description
zone_id Yes Parent zone ID, typically dtm_zone.<name>.id. Changing it replaces the record.
name Yes Name relative to the zone, e.g. web. Use @ for the zone apex.
type Yes A, AAAA, CNAME, MX, NS, PTR, SRV, TXT, CAA, or ALIAS. Changing it replaces the record.
ttl No TTL in seconds. 0 inherits the zone’s default_ttl.
enabled No Defaults to true. A disabled record is kept but not served.
protected No Defaults to false. A protected record cannot be deleted, or replaced by a colliding create, rename, or import, without an explicit force.
values (block, 1+) Yes One or more values (see below).
lb_policy (block) No Load-balancing policy. See health checks and load balancing.
health_check_template (block) No Health-check template applied per value. See health checks and load balancing.

Each values block holds one backend:

values {
value = "10.0.1.10"
region = "northeurope" # optional; used by region-aware LB
}
Value attribute Description
value The record data: an IP for A/AAAA, a hostname for CNAME/MX/NS/ALIAS, text for TXT.
region Optional region tag for region-aware load balancing.
tag CAA only: issue, issuewild, or iodef.
priority MX preference / SRV priority (and the CAA flag octet, 0 to 255).
weight SRV weight only. The weighted load-balancing method uses lb_policy.value_weights instead, not this field.
port SRV target port.

dtm_auto_record (role: editor or admin) manages a record whose value list is owned by DTM: tag-based VM registration fills in the addresses as your fleet changes, while everything you declare here (TTL, LB policy, health template, enabled, protected) stays yours. The resource has no values attribute at all, so a plan can never diff on a list DTM owns.

resource "dtm_auto_record" "api" {
zone_id = dtm_zone.example.id
name = "api"
type = "A" # A or AAAA only
ttl = 30
lb_policy { method = "round-robin" }
health_check_template {
protocol = "https"
port = 443
path = "/healthz"
}
}

Attributes match dtm_record minus values, and type accepts only A and AAAA. lb_policy.value_weights is rejected: weights key on addresses DTM will change. A freshly created record has no values and answers NODATA until a tagged VM claims the name.

# CNAME (exclusive at a name, cannot sit at the apex)
resource "dtm_record" "www" {
zone_id = dtm_zone.example.id
name = "www"
type = "CNAME"
values { value = "web.example.internal." }
}
# MX with priorities
resource "dtm_record" "mail" {
zone_id = dtm_zone.example.id
name = "@"
type = "MX"
values {
value = "mx1.example.internal."
priority = 10
}
values {
value = "mx2.example.internal."
priority = 20
}
}
# TXT
resource "dtm_record" "spf" {
zone_id = dtm_zone.example.id
name = "@"
type = "TXT"
values { value = "v=spf1 include:_spf.example.internal -all" }
}
# SRV
resource "dtm_record" "sip" {
zone_id = dtm_zone.example.id
name = "_sip._tcp"
type = "SRV"
values {
value = "sipserver.example.internal."
priority = 10
weight = 60
port = 5060
}
}
Terminal window
# Records import as "<zone_id>:<record_id>"
terraform import dtm_record.web 8f3a...:c21b...
# Zones import as "<zone_id>"
terraform import dtm_zone.example 8f3a...