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.
Before you start
Section titled “Before you start”- 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.
Deployment permissions
Section titled “Deployment permissions”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.
What the deployment creates
Section titled “What the deployment creates”Everything DTM needs lands in one resource group (see the recommended layout):
- DTM nodes (Linux VMs) running the
dtm-serverservice, 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.netprivate 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 the managed identity can do
Section titled “What the managed identity can do”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.
How the cluster’s secrets are created
Section titled “How the cluster’s secrets are created”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).
Key Vault network access
Section titled “Key Vault network access”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 aprivatelink.vaultcore.azure.netprivate 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.netzone 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.
- If your landing zone owns the privatelink zones centrally, pass the
resource ID of your existing
ServiceEndpoint: the public endpoint stays, behind a firewall that admits only the node subnets. Each node subnet must already have theMicrosoft.KeyVaultservice 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.
Recommended: a dedicated resource group per region
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:
Sizing and storage
Section titled “Sizing and storage”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.
Scaling the data disk
Section titled “Scaling the data disk”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.
Network access (NSG)
Section titled “Network access (NSG)”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.)
First login
Section titled “First login”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:
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 tsvThe 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.
Break-glass OS access
Section titled “Break-glass OS 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:
-
On the node’s NSG, add a temporary inbound rule allowing TCP
22from your admin CIDR only, at a free priority below4000so 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
AzureBastionSubnetprefix 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 priority3000. -
ssh <your-admin-user>@<node-private-ip>with the key you chose at deployment. -
Do the task. If it calls for a service restart, restart one node at a time.
-
Remove the rule (on Terraform, empty
ssh_breakglass_source_cidrsand apply):Terminal window az network nsg rule delete \--resource-group <resource-group> --nsg-name <node-nsg> \--name break-glass-ssh
Choose how you will manage DTM
Section titled “Choose how you will manage DTM”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.
Deploying with Bicep
Section titled “Deploying with Bicep”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:
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.
Bring your own resources (Terraform)
Section titled “Bring your own resources (Terraform)”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,
applyruns 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 toaz, 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.
Verify the cluster
Section titled “Verify the cluster”Check each node reports ready and the cluster sees all its members. Readiness needs no token:
# 200 = ready to serve; 503 = still starting or joiningcurl -sk https://<node-ip>:8443/readyzTo 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:
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:
dig @<node-ip> example.internal +shortNext steps
Section titled “Next steps”- Quickstart: create your first zone and record.
- Users and access: add administrators and set up Entra ID SSO.
- Adding nodes: scale the cluster out for more capacity or resilience.
- Observability: scrape metrics into Prometheus and import the Grafana dashboard.