Skip to content

Migrating to Hayami DTM

DTM imports standard zone files, so you can bring existing DNS in from almost any source. The lossless path for every source is: export to an RFC 1035 zone file, then upload it. Coming from BIND specifically? There is a dedicated BIND 9 guide that maps named.conf constructs and adds a reversible cutover plan on top of this page.

Import from the Zones → Import wizard in the web UI (it auto-detects the format), or over the API. Preview first with a dry run. The API examples send a bearer token as $DTM_TOKEN; the API overview shows how to capture one.

Source Export
BIND 9 Zone files are already on disk (see the directory in named.conf).
Windows DNS Export-DnsServerZone -Name <zone> -FileName <file> (writes to %SystemRoot%\System32\dns\).
Azure DNS (public) az network dns zone export -g <rg> -n <zone> -f <file>.
Azure Private DNS az network private-dns zone export -g <rg> -n <zone> -f zone.txt (writes a BIND zone file, SOA included).
Cloudflare Dashboard → the zone → DNS → Records → Advanced → Export.
Google Cloud DNS gcloud dns record-sets export <file> --zone-file-format --zone=<name>.
Route 53 cli53 export <zone> > <file> (Route 53’s native export is JSON, not a zone file).

Azure Private DNS: direct import (no export file)

Section titled “Azure Private DNS: direct import (no export file)”

For Azure Private DNS there is also a lower-friction path: DTM reads the zone straight from Azure using the node’s managed identity, no export file needed. Create the destination zone on DTM first, then:

Terminal window
curl -sk -X POST https://dtm.internal:8443/api/v1/zones/<zone-id>/import-azure-privatedns \
-H "Authorization: Bearer $DTM_TOKEN" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: application/json" \
-d '{
"subscription_id": "00000000-0000-0000-0000-000000000000",
"resource_group": "rg-dns",
"private_zone_name": "corp.example",
"dry_run": true
}'
  • Admin role; the managed identity needs read access to the Azure zone.
  • One-way and read-only: DTM only ever reads from Azure.
  • Maps A, AAAA, CNAME, MX, PTR, SRV, TXT; skips SOA and NS (DTM manages those) and unknown types.
  • Idempotent: unchanged records are skipped, so you can re-run it while cutting over. CNAME coexistence conflicts are reported, not written.
  • Set "dry_run": true (a body field) to preview created / updated / unchanged / conflicts without writing, then re-send with "dry_run": false to commit.

The Content-Type selects the parser:

  • multipart/form-data - one or more BIND zone files, one zone per file.
  • text/dns - a single BIND zone file body.
  • application/json - a DTM JSON bundle (preserves load-balancing policy and health-check templates, but not health-check template secrets, see below).
  • text/csv - a spreadsheet (lossy: no LB policy or health-check templates; use for initial onboarding only). Columns: zone, name, type, ttl, value, priority, weight, port, region.
Terminal window
# Upload a single exported zone file
curl -sk -X POST https://dtm.internal:8443/api/v1/zones/import \
-H "Authorization: Bearer $DTM_TOKEN" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: text/dns" \
--data-binary @example.internal.zone

Add ?dry_run=true (or click Preview in the wizard) to parse the input and diff it against the current state, returning the planned changes without writing anything:

Terminal window
curl -sk -X POST "https://dtm.internal:8443/api/v1/zones/import?dry_run=true" \
-H "Authorization: Bearer $DTM_TOKEN" \
-H "X-DTM-CSRF: 1" \
-H "Content-Type: text/dns" \
--data-binary @example.internal.zone
  • Limits: 20 MB per request, 50 zones per request, 50,000 records per zone. Split large migrations across multiple requests.
  • Idempotent: import is safe to re-run. A zone is created if it does not exist and records are added without duplicating, so you can iterate.
  • Preserve almost everything: to migrate a DTM cluster to another DTM cluster (including LB policy and health checks), export the JSON bundle with GET /api/v1/zones/export and import it with Content-Type: application/json. Health-check template secrets (headers values, oauth2.client_secret, mtls.key_pem) are masked as *** in the export and are not restored by the import: re-apply them on the destination cluster, or authenticated probes will fail and their backends will drop out of answers.