Skip to content

Deploy from the Azure Marketplace

Hayami DTM is delivered exclusively through the Azure Marketplace and runs inside your own subscription. Deployment provisions a small cluster of Linux VMs in a VNet you choose; DNS queries and answers never leave your environment.

This page covers the deployment at an operator level. The Marketplace offer walks you through the parameters; the notes below explain what each one is for and how to confirm the result.

  • An Azure subscription and a target resource group, with enough rights to both create resources and assign roles there. See Deployment permissions below; this is the most common reason a first deploy fails.
  • An existing VNet for the DTM nodes and a free address range: the deployment creates a small dedicated subnet in that VNet (or uses one you name), which your other VNets can reach for DNS. The deployment does not create the VNet itself.
  • For the four-node, two-region plan: an existing VNet in each region, with address spaces that do not overlap, already connected by VNet peering or through a hub, VPN, or ExpressRoute, so that all four nodes can reach each other on TCP and UDP port 7946. The wizard does not create or change any peering, so connect the two VNets before you deploy. If they cannot reach each other, the two regions cannot form one cluster.
  • A decision on cluster size. Run two or more nodes for high availability and zero-downtime upgrades. A single node has no failover.
  • A decision on your cluster boundary. Every node in a cluster must be able to route to every other node: replication is peer-to-peer, with no leader and no proxy, so one cluster spans exactly one routable domain (a VNet, peered VNets, or VNets joined by a transit connection such as hub-and-spoke or Virtual WAN). Estates that cannot reach each other need a separate cluster each; a node that cannot reach its peers keeps serving DNS from its own copy while quietly diverging, so this is a plan-it-up-front decision, not a recoverable-later one.

Deploying DTM does two distinct things in your target scope: it creates resources (VMs, a managed identity, a Key Vault, disks, a NIC, and an NSG), and it creates role assignments to grant each node identity the roles it needs (see the rights table below). You need permission for both.

In Azure terms that means Owner, or Contributor plus User Access Administrator, on the target resource group or subscription.

Everything DTM needs lands in one resource group (see the recommended layout):

  • DTM nodes (Linux VMs) running the dtm-server service, joined into one cluster.
  • A dedicated Premium SSD data disk per node, attached automatically and mounted at the database path. See Sizing and storage.
  • A Key Vault, dedicated to the deployment, holding the initial administrator password and the cluster encryption key. It is private by default: public network access is disabled, and the nodes reach it through a private endpoint in the node subnet, resolved by a privatelink.vaultcore.azure.net private DNS zone linked to the node VNet. See Key Vault network access.
  • One system-assigned managed identity per node: the node’s own Azure identity, with no secret to store or rotate. Its rights are listed below.
  • A network security group (NSG), attached to each node’s network interface, that admits the DTM ports from inside your network only. See Network access.
  • Stable private IPs for the nodes, so client DNS settings never need to change across upgrades or node replacements.
  • The role assignments each node identity needs (the table below).

There are no public IPs. DTM runs entirely on private addresses inside your VNet.

What one Marketplace deployment creates: DTM nodes on stable private IPs in a dedicated subnet, each with its own Premium SSD data disk and system-assigned managed identity, an NSG on each node's network interface, and a dedicated Key Vault that the primary node publishes the cluster secrets to at first boot and every other node reads them from; a numbered panel details the role assignments and NSG rules matching the numbered markers in the diagram. What one Marketplace deployment creates: DTM nodes on stable private IPs in a dedicated subnet, each with its own Premium SSD data disk and system-assigned managed identity, an NSG on each node's network interface, and a dedicated Key Vault that the primary node publishes the cluster secrets to at first boot and every other node reads them from; a numbered panel details the role assignments and NSG rules matching the numbered markers in the diagram.

Each node authenticates to Azure as its own system-assigned managed identity, so there are no credentials stored on disk. The deployment grants that identity exactly the roles the node needs and nothing more:

Role Scope Why the node needs it
Reader The DTM resource group Region discovery: region-aware load balancing enumerates which region each node is in.
Contributor The DTM resource group Lifecycle and stable-IP failover: the cluster can reassign a node’s private IP so DNS endpoints stay stable when a node is replaced.
Key Vault Secrets User The deployment’s Key Vault Every node reads the admin password and cluster encryption key from the vault on boot.
Key Vault Secrets Officer The deployment’s Key Vault The primary node only, so it can publish the secrets it generates on first boot.

The Contributor grant is deliberately scoped to the DTM resource group alone, so the cluster can only ever act on its own resources. That is one reason to give DTM a resource group of its own.

On the Marketplace appliance, the cluster’s shared secrets, the initial administrator password and the cluster encryption key, are generated on the VM at first boot, written to Key Vault, and never exposed in the deployment template or its outputs. The primary node generates them and publishes them to the vault (that is what its Key Vault Secrets Officer grant is for); every other node reads the same values back with its Key Vault Secrets User grant, so the whole cluster shares one set. The management TLS certificate is generated locally on each node at first boot on this path and is not stored in the vault.

Deploying with Terraform handles the administrator password and the cluster encryption key the same way, generating them on the node and into Key Vault so they never land in Terraform state. The copy-ready Terraform samples also generate the management TLS certificate locally on each node, as above. The Terraform customer-starter environment (see bring your own resources) additionally has the primary publish a shared web UI TLS bundle to the vault (the dtm-tls secret) that the other nodes read, so it serves one cluster-wide UI certificate (the API listener’s certificate is separate). See TLS certificates.

Terraform itself never reads or writes a secret in the vault, so no secret value enters Terraform state. A secret only you can provide, such as a CA-signed UI certificate, is one you create in the vault yourself, as a manual step before the nodes boot; Terraform takes only its name. See supply the certificate at deploy time.

For the life of these secrets after day one, see rotating secrets and TLS certificates (the first-boot certificate is valid for two years).

Every deployment path creates the cluster’s Key Vault private by default, and offers two alternatives. The Marketplace wizard has a Key Vault step for this. The templates take one parameter:

Path Mode parameter Existing DNS zone parameter
Marketplace (ARM) and Bicep keyVaultNetworkAccess keyVaultPrivateDnsZoneId
Terraform key_vault_network_access key_vault_private_dns_zone_id
  • Private (default): public network access is disabled. The vault gets a private endpoint in the primary node’s subnet. By default the deployment also creates a privatelink.vaultcore.azure.net private DNS zone and links it to every node VNet (both regions on a two-region plan).
    • If your landing zone owns the privatelink zones centrally, pass the resource ID of your existing privatelink.vaultcore.azure.net zone instead, already linked to the node VNets. The deployment then only adds the endpoint’s record to it. You must do this if a node VNet is already linked to such a zone: Azure refuses to link one VNet to two zones of the same name.
  • ServiceEndpoint: the public endpoint stays, behind a firewall that admits only the node subnets. Each node subnet must already have the Microsoft.KeyVault service endpoint, because the deployment does not configure one. Without it Azure refuses the vault’s network rule, naming the missing service endpoint (SubnetsHaveNoServiceEndpointsConfigured).
  • Public: the public endpoint is open. Meant for a short evaluation where private DNS is not available.

In every mode the vault is RBAC-only, and only the node identities hold data-plane roles on it. Creating the vault, its endpoint and its role assignments is control-plane work, so you can deploy (and run terraform plan) from outside the VNet in every mode. To read a secret, run a command on a node, as in first login, rather than reading the vault from your machine.

A node added later reads the vault at its first boot. In the cluster’s VNet it resolves the private endpoint as it is. A node in a different, peered VNet needs that VNet linked to the cluster’s privatelink.vaultcore.azure.net zone first. With ServiceEndpoint, its subnet needs the service endpoint and a rule on the vault. See Adding nodes.

Section titled “Recommended: a dedicated resource group per region”

Give DTM its own, new resource group for each region it runs in, holding all of that region’s DTM resources. It keeps things clean in three ways:

  • Clean lifecycle. DTM’s resources are not tangled up with anything else.
  • Least privilege. The node identity’s Contributor grant only ever covers DTM’s own resources, never your wider estate.
  • Simple teardown. Removing a regional deployment is just deleting its resource group.

For a two-region deployment, that means one dedicated resource group per region. Here is what a two-region deployment looks like:

A two-region deployment, one dedicated resource group per region: rg-ne in northeurope and rg-we in westeurope. Each holds a DTM node on a stable private IP (Standard_D2s_v5), an NSG listing the deploy port table (53 DNS, 853 DoT if enabled, 443 DoH if enabled, 8080 Web UI, 8443 API and management, 7946 replication between nodes only), a Premium SSD data disk (64 GiB P6, grows in place, host caching none), and a system-assigned managed identity with Reader and Contributor scoped to that resource group plus Key Vault Secrets User; the primary's identity also holds Secrets Officer. The nodes replicate over TCP and UDP 7946, and one Key Vault in the primary region's group is read by both identities. A two-region deployment, one dedicated resource group per region: rg-ne in northeurope and rg-we in westeurope. Each holds a DTM node on a stable private IP (Standard_D2s_v5), an NSG listing the deploy port table (53 DNS, 853 DoT if enabled, 443 DoH if enabled, 8080 Web UI, 8443 API and management, 7946 replication between nodes only), a Premium SSD data disk (64 GiB P6, grows in place, host caching none), and a system-assigned managed identity with Reader and Contributor scoped to that resource group plus Key Vault Secrets User; the primary's identity also holds Secrets Officer. The nodes replicate over TCP and UDP 7946, and one Key Vault in the primary region's group is read by both identities.

Each node runs on Standard_D2s_v5 (2 vCPU, 8 GiB) by default, the smallest size that supports Accelerated Networking for line-rate DNS. Larger sizes suit higher query or dynamic-update rates, though the cluster also scales horizontally: adding nodes is the usual way to add both capacity and resilience.

Each node keeps its full copy of DNS state in a single embedded database on a dedicated Azure Premium SSD managed disk mounted at /var/lib/dtm, attached automatically by the deployment:

Setting Default Why it matters
Disk type Premium SSD (managed) Durable; survives stop, restart, and host maintenance.
Size 64 GiB For durability and compaction headroom, not raw capacity.
Redundancy Locally redundant (LRS) Zone-redundant (ZRS) where the region supports it.
Host caching None Every write reaches durable media before it is acknowledged.

The database is never placed on the VM temporary disk, which Azure wipes on deallocation. Host caching is disabled so a write is durable the moment it is acknowledged, which is what protects your data from a host failure. A DNS control plane’s database is small (megabytes to a few gigabytes even for large estates), so the 64 GiB default leaves ample room for routine compaction; raise the disk to a larger Premium SSD tier if you need more IOPS. These are deploy-time defaults you can adjust, not settings you have to tune by hand.

If you need more IOPS headroom, raise the deployment’s data-disk size parameter and re-apply. The resize is online: Azure grows the disk in place with the VM running, with no restart and no data movement. Moving from the 64 GiB default (P6) to 128 GiB (P10) doubles the IOPS budget, and 256 GiB (P15) roughly doubles it again. The same disk configuration and procedure apply to single-region and dual-region deployments.

Two things to know before you resize:

  • Resize through the deployment parameter, never the Azure portal. A portal resize works at the time, but your template still records the old size, so the next deployment or apply tries to shrink the disk and Azure rejects it.
  • Disks only grow. Azure managed disks cannot shrink, so start at the default and scale up when monitoring says you need it.

Most operators resize purely for IOPS, since the database itself stays small. If you do want the added capacity available to the filesystem, grow it live with sudo resize2fs /dev/disk/azure/data/by-lun/0 once lsblk shows the new size. The filesystem spans the whole data disk, so there is no partition to grow first, and no service restart is needed.

DTM runs entirely on private IPs inside your VNet. The nodes are not assigned public IPs, and you should not add one: DNS and management stay on your private network, where your clients and administrators already are. Reach the nodes over the VNet, a peered VNet, or your existing private connectivity (VPN / ExpressRoute).

Open these ports to the appropriate sources on the network security group attached to each node’s network interface (the Marketplace deployment attaches the NSG to the NIC, not the subnet):

Port Protocol Purpose Source
53 UDP + TCP DNS queries Your VNets / DNS clients
8443 TCP REST API and management Administrators / automation
8080 TCP Web UI (HTTPS; self-signed certificate at first boot, replaceable) Administrators
7946 UDP + TCP Cluster replication between nodes The cluster’s subnet(s) only
853 TCP DNS-over-TLS (if enabled) Your DNS clients
443 TCP DNS-over-HTTPS (if enabled; a dedicated listener serving only /dns-query) Your DoH resolver clients

In the Marketplace (ARM) and Bicep templates, and in the Terraform single-region and dual-region samples, the deployment creates this NSG for you and scopes the management ports (8443 and 8080) to the adminAddressPrefix parameter (admin_address_prefix in Terraform), which defaults to VirtualNetwork (the whole VNet, including peered traffic). Tighten it to the subnet or CIDR your administrators and automation actually use, such as a jumpbox or management subnet. The deployment enforces the value with an explicit deny rule, so once narrowed, other workloads inside the VNet cannot reach the management ports even though they share the network.

The Terraform customer-starter sample has no admin_address_prefix equivalent: its NSG admits 8443 and 8080 from the whole VNet, with no deny rule for other in-VNet sources. To restrict them there, bring your own NSG: set create_nsg = false (and existing_nsg_id if you want DTM to attach it), then allow only your administrator sources to those ports.

SSH (22) is closed. Azure’s default rules would admit it from anywhere in the VNet or a peered network, so the deployment adds an explicit deny, deny-ssh-vnet at priority 4010. Day-to-day operation never needs SSH; for the rare task that does, see break-glass OS access.

The cluster port (7946) is allowed from the cluster’s own subnet (on a dual-region deployment, both regions’ subnets), then denied from the rest of the VNet and peered networks by deny-gossip-vnet at priority 4020. A node added later in the same subnet joins with no NSG change. When you add a node in a different subnet or region, enter that subnet in the add-node wizard’s Other cluster subnets field, and add it to the existing nodes’ cluster-port allow rules. (Added in v1.0.4; a cluster deployed earlier keeps its NSG, which has no such deny, until redeployed.)

The deployment output tells you the API endpoint (for example https://<node-ip>:8443), the web UI URL, and a recoverAdminAccessCommand output that fetches the initial administrator password for you (the Terraform starter’s equivalent output is retrieve_admin_password_command; the Bicep dialect names it retrieveAdminPasswordCommand).

The initial administrator is:

  • Username: admin
  • Password: generated on the primary node at first boot and written to the Key Vault the deployment provisioned, as the secret dtm-initial-admin-password. See how the cluster’s secrets are created.

Retrieve the password by running the recoverAdminAccessCommand the deployment printed. It has this shape:

Terminal window
az vm run-command invoke \
--resource-group <resource-group> --name <primary-node-vm> \
--command-id RunShellScript \
--scripts "/usr/local/sbin/kv-secret <your-vault> dtm-initial-admin-password" \
--query "value[0].message" --output tsv

The command runs on the primary node, which reads the vault with its own managed identity. That is why it works while the vault is private: a read from your own machine (az keyvault secret show) is refused by a private vault, whatever role you hold on it.

This is the one place the docs send you to az vm run-command, and it is deliberate. The password is generated on the node and read on the node, so it never passes through your deployment tooling, your deployment history, or Terraform state. Everything else runs through the web UI, the API, or Terraform, and the rare exception through break-glass OS access.

  • What you need: permission to run commands on the VM, for example Virtual Machine Contributor on it. You need no role on the Key Vault.
  • Where the password appears: under [stdout] in the output.
  • If it answers HTTP 404: the primary has not finished its first boot yet. The read never creates the secret, so wait a minute and run it again.

Log in to the web UI on port 8080 with admin and that password. Change the initial password immediately, then create named users with the least privilege each one needs. See users and access.

The deployment form asks for one more credential: a Linux OS admin username and an SSH public key. This is the break-glass login for the VM’s operating system, and it is deliberately unrelated to the DTM administrator password above: you choose and hold the OS key, and DTM generates its own admin password into Key Vault. The OS has no password login. The image accepts SSH keys only, root login is disabled, and logins are restricted to the admin username you chose. The Terraform path takes the same inputs.

Day-to-day operation never needs a shell. Zones, records, and settings are managed through the web UI, the API, and Terraform, and diagnostics come from the support bundle and the debug log in the UI. The shell exists for the rare tasks the docs explicitly mark as break-glass: the file-only boot settings, certain backup-restore and disaster-recovery steps, and troubleshooting a node whose API is down.

SSH is closed by default on every deployment route (see network access). When one of those tasks arises, the ritual is deliberately short:

  1. On the node’s NSG, add a temporary inbound rule allowing TCP 22 from your admin CIDR only, at a free priority below 4000 so it is evaluated before the deployment’s deny:

    Terminal window
    az network nsg rule create \
    --resource-group <resource-group> --nsg-name <node-nsg> \
    --name break-glass-ssh --priority 3000 \
    --direction Inbound --access Allow --protocol Tcp \
    --destination-port-ranges 22 --source-address-prefixes <admin-cidr>

    Connecting through Azure Bastion? Use your AzureBastionSubnet prefix as the source. Bastion connects from inside the VNet, so the deny applies to it as well, and it needs the same temporary rule.

    Deployed with Terraform? Do not add the rule by hand: Terraform owns that NSG’s rules, and the next apply would delete it. Set ssh_breakglass_source_cidrs = ["<admin-cidr>"] and apply instead, which creates the same allow at priority 3000.

  2. ssh <your-admin-user>@<node-private-ip> with the key you chose at deployment.

  3. Do the task. If it calls for a service restart, restart one node at a time.

  4. Remove the rule (on Terraform, empty ssh_breakglass_source_cidrs and apply):

    Terminal window
    az network nsg rule delete \
    --resource-group <resource-group> --nsg-name <node-nsg> \
    --name break-glass-ssh

DTM has three management surfaces. Pick whichever fits how you work; you can use more than one, but manage any given object in a single place (see the Terraform notes on avoiding drift).

Surface Best for Needs an API token?
Web UI Interactive changes, browsing cluster state No, just log in
Terraform Declarative, version-controlled, repeatable config Yes
REST API Scripting and integrations Yes

Only the Terraform and REST API paths need an API token; web UI users never need one. The Quickstart walks each path through creating your first zone and record.

For Bicep-first estates (Azure Landing Zones), DTM also ships Bicep entry points that share the Marketplace templates’ parameters and contracts: single-region, dual-region, and add-node under deploy/bicep/, available from support (the HA Marketplace topologies are ARM-only today), with the same parameters (imageVersion, adminAddressPrefix, dataDiskSizeGB, and friends), the same NSG rules, and the same secrets-generated-on-the-VM contract. A regionDiscoveryEnabled parameter (default true) controls whether the node runs Azure region discovery; setting it false also skips the local resource-group Reader grant, whose only consumer is the discovery loop. Preview with az deployment group what-if and deploy by copying the example parameter file, editing its values, and pointing both flags at the same directory: cp single-region/main.example.bicepparam single-region/main.bicepparam, then az deployment group create -g <rg> -f single-region/main.bicep -p single-region/main.bicepparam. The modules follow Azure Verified Modules conventions (tags, locks, extra role assignments, diagnostics; telemetry off) but are not registry-published AVM modules, and the Azure Marketplace offer itself remains the supported packaging: Bicep is an authoring alternative, not a different product channel.

One extra template sits apart from the topologies: deploy/bicep/mg-grant/ grants Reader at management-group scope to your node identities, for management-group discovery scopes. It is separate because a role assignment can only be created at or below the deployment’s target scope, and the topologies deploy into a resource group. Deploy it after the cluster, with the principal IDs the topology outputs:

Terminal window
az deployment mg create \
--management-group-id <mg-name> \
--location <region> \
--template-file deploy/bicep/mg-grant/main.bicep \
--parameters principalIds="['<managedIdentityPrincipalId>']"

--location only records where the deployment metadata lives; nothing regional is created. The caller needs Owner or User Access Administrator at the management group: Reader on it is not enough to grant Reader on it.

By default the Terraform customer-starter environment creates everything DTM needs: a dedicated resource group, subnet, NSG, Key Vault, and system-assigned identities with their role grants. For estates where the platform team owns those, each is an independent bring-your-own toggle; bring only what your security model requires:

You can bring Toggles
Subnet and NSG create_subnet / existing_subnet_id, create_nsg / existing_nsg_id (you own the rules DTM would otherwise manage)
User-assigned identity and Key Vault create_identity / existing_identity_id, create_key_vault / existing_key_vault_id (a preflight checks the grants you pre-created)
Resource group create_resource_group / existing_resource_group_name (advanced)

(The VNet is always yours: the template only ever references an existing VNet, carving a dedicated subnet in it unless you bring that too.)

Two properties make this safe to adopt: the toggles are additive (all defaults means create-everything, exactly as before), and teardown only ever removes resources DTM created, never a resource group, VNet, Key Vault, or identity you brought; every role assignment DTM created on your scopes is cleanly removed, even when the scope itself is one you brought. The delivered artifacts include a prerequisites checklist covering what to pre-create and pre-grant for each tier, including a per-tier decision table; the least-privilege combination is bringing your own identity and Key Vault, which reduces the deploy principal’s requirement to plain Contributor.

Details worth knowing before you rely on them:

  • The preflight fails fast, not silently. In BYO-identity mode, apply runs a grant check before creating any VM and fails with a clear message if the identity lacks a write-capable Key Vault role on the BYO vault, turning an under-grant from silent boot retries (the node’s bootstrap retries about once a minute until it succeeds, with no time limit) into an actionable error. It shells out to az, so run Terraform from a host with the Azure CLI logged in (Linux, macOS, WSL, or Cloud Shell). The RG-scoped Reader and Contributor grants are not preflight-checked (they can only be verified at runtime); apply them before the cluster boots or region discovery and the lifecycle orchestrator sit inert.
  • Shared resource groups widen the blast radius. The node identities get Contributor on the deployment resource group so the lifecycle orchestrator can swap VMs; on a shared BYO RG that grant spans everything else in it. Strongly prefer a dedicated RG per region plus a BYO identity, so the grant is scoped and pre-audited. The plan fails early if the existing RG’s region does not match the deployment’s.

Check each node reports ready and the cluster sees all its members. Readiness needs no token:

Terminal window
# 200 = ready to serve; 503 = still starting or joining
curl -sk https://<node-ip>:8443/readyz

To check cluster membership over the API, capture a token first. Login sets the JWT as an HttpOnly dtm_token cookie; capture it from the Set-Cookie header and pass it as a bearer token:

Terminal window
TOKEN=$(curl -sk -X POST https://<node-ip>:8443/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "<initial-password>"}' \
-D - -o /dev/null | grep -i '^set-cookie: dtm_token=' \
| sed -E 's/.*dtm_token=([^;]+).*/\1/')
# Every node you deployed should be listed, each "alive". Count them: a
# failed node drops out of the list rather than changing status.
curl -sk https://<node-ip>:8443/api/v1/nodes \
-H "Authorization: Bearer $TOKEN"

Then confirm DNS answers from a node:

Terminal window
dig @<node-ip> example.internal +short