Skip to content

Cluster and runtime config with Terraform

These singleton resources configure runtime behaviour, and their scope differs: dtm_transport_config and dtm_query_log_config are cluster-wide, so you declare each once and it replicates to every node. The observability (OTLP), rate-limit, cache, and replication-timing singletons are per node: each configures only the node the provider endpoint targets, so apply them against every node’s endpoint for cluster-consistent behaviour. All require the admin role. Applying one never restarts DTM; dtm_replication_config values persist on apply but only take effect at the endpoint node’s next restart (noted below).

dtm_otlp_config turns on DTM’s OTLP/HTTP metrics export to a collector you manage. See observability.

resource "dtm_otlp_config" "this" {
enabled = true
endpoint = "https://otel-collector.internal:4318"
interval_seconds = 30
headers = {
"x-api-key" = var.otlp_api_key # sensitive
}
}
Attribute Description
enabled Default false.
endpoint Collector URL. Required when enabled = true.
headers Export headers (values are secrets, stored encrypted, redacted on read).
interval_seconds Export interval (0 = exporter default).
insecure Default false. Push metrics over plaintext HTTP with no TLS at all (the OTLP-standard “insecure”). Not a skip-cert-verify switch: a self-signed HTTPS collector still needs a trusted certificate. An explicit false clears the flag on the server.

dtm_rate_limit_config sets per-IP API rate limits (token bucket) for the auth, write, and read request classes. A value of 0 uses the built-in default for that class. The limiter’s fourth class, upload (the restore/compact disk-staging endpoints), is not settable via Terraform, and an apply of this resource resets any upload values previously set via the API back to the built-in default; re-apply custom upload tuning with PUT /api/v1/config/rate-limits afterwards.

resource "dtm_rate_limit_config" "this" {
auth_rps = 5
auth_burst = 5
write_rps = 60
write_burst = 20
read_rps = 10
read_burst = 30
}

dtm_cache_config controls the cache for forwarded answers.

resource "dtm_cache_config" "this" {
enabled = true
max_size = 10000
ttl_seconds = 0 # 0 = use each record's own TTL
}

max_size must be greater than 0 when enabled = true.

dtm_delete_guardrail_config caps how many records one bulk delete may remove, the same guardrail the bulk-delete API enforces.

resource "dtm_delete_guardrail_config" "this" {
bulk_delete_cap = 250
}
Attribute Description
bulk_delete_cap Maximum records one bulk delete may remove. 0 or unset uses the built-in default of 100; a negative value is rejected. The server normalises the read-back to the effective cap.

Singleton: import with ID delete-guardrails. Removing the resource resets the cap to the default rather than removing the guardrail.

dtm_transport_config manages the encrypted inbound DNS listeners, the same setting as the UI’s Transport Protocols card and PUT /api/v1/transport-config. Applies live and replicates cluster-wide; see DNS over TLS and HTTPS for the security caveats before enabling either.

resource "dtm_transport_config" "this" {
dot_enabled = true
dot_listen_addr = ":853"
doh_enabled = false
}

Singleton: import with ID transport-config.

dtm_query_log_config turns per-query logging on and off at runtime, cluster-wide, without a restart. sample_rate keeps 1-in-N queries, worth setting on high-QPS nodes.

resource "dtm_query_log_config" "this" {
enabled = true
sample_rate = 10 # keep 1 in 10 queries
}

Singleton: import with ID query-log. The observability.query_log_* file keys are only the boot seed; once this resource has applied, the persisted value wins over the YAML at the next boot.

The companion singleton dtm_region_discovery_config lives with the other region resources; see regions, discovery and subnets.

dtm_replication_config tunes how quickly cluster state spreads between nodes and how often nodes exchange a full sync. Intervals are duration strings ("30s", "200ms"); the gossip_* attribute names come from the underlying replication protocol.

resource "dtm_replication_config" "this" {
push_pull_interval = "30s"
gossip_interval = "200ms"
gossip_nodes = 3
probe_interval = "1s"
probe_timeout = "500ms"
}

Local users and the Entra SSO configuration are also manageable as code (both admin), with the same secrets-never-in-state discipline as everything else: the credential fields are write-only arguments (Terraform 1.11 or newer), sent to the API on apply and never persisted to state or plan.

dtm_user manages a local user and their role:

resource "dtm_user" "alice" {
username = "alice"
role = "editor" # admin | editor | readonly
enabled = true
password_wo = var.alice_password # sensitive variable
password_wo_version = "1"
}
  • Rotating the password means changing password_wo and bumping password_wo_version together; a write-only value is invisible to the diff, so changing it alone is a no-op.
  • username forces replacement (there is no rename). Import by username (terraform import dtm_user.alice alice), then add the password fields in config to manage the credential.
  • The server-side guards still apply: you cannot disable or demote the last remaining admin, and a user cannot disable their own account.
  • Entra-provisioned users (source = "entra") are not created here; manage their access through the app registration.

dtm_sso_config manages the Entra ID SSO configuration of the node the provider endpoint targets (per node today: on a multi-node cluster, apply it through each node’s endpoint, for example one provider alias per node, or Sign in with Microsoft works on one node only; a destroy likewise disables SSO on that node only):

resource "dtm_sso_config" "entra" {
enabled = true
tenant_id = "00000000-0000-0000-0000-000000000000"
client_id = "11111111-1111-1111-1111-111111111111"
redirect_uri = "https://dtm.example.internal/api/v1/auth/entra/callback"
client_secret_wo = var.entra_client_secret # sensitive variable
client_secret_wo_version = "1"
}

The client secret follows the same write-only pattern: rotate by changing the value and bumping its version together. Role mapping is app-role based (the app registration’s Admin role maps to DTM admin), so there is no claims-mapping argument here.

Two data sources expose live cluster state:

# All cluster members
data "dtm_nodes" "all" {}
# One-shot cluster summary
data "dtm_cluster_info" "this" {}
output "alive_nodes" {
value = data.dtm_cluster_info.this.alive_count
}

dtm_nodes returns a nodes list, each with id, name, addr, region_id, status (alive / suspect / dead / left), and version. dtm_cluster_info returns version, node_count, alive_count, node_id, and region_id.