Deploy a cluster with Bicep
This page deploys the DTM cluster itself with Bicep, from the Azure Marketplace image, into a VNet you already own. It is the sibling of Deploy a cluster with Terraform; the samples are the same deployments in the other language.
Which deployment path is yours
Section titled “Which deployment path is yours”| You want | Use |
|---|---|
| Azure to create the resource group, subnet and peering for you | The Marketplace wizard, whose ARM templates create them |
| To deploy into a subnet your network team already owns, as code | This page |
| Terraform rather than Bicep | Deploy a cluster with Terraform |
The samples here deliberately do not create VNets, subnets or peerings. They take the resource id of a subnet that already exists, which is the normal shape for an Azure Landing Zone where the platform team owns connectivity. The Marketplace ARM templates remain the path that builds the network for you.
Before the first deployment
Section titled “Before the first deployment”Accept the Marketplace terms once per subscription. Azure refuses to create a VM from a Marketplace image until the offer’s terms are accepted for that subscription:
az vm image terms accept \ --publisher hayami --offer dtm --plan dtm-payg-v1Without it the deployment fails at VM create with
MarketplacePurchaseEligibilityFailed or VMMarketplaceInvalidInput.
You will also need a subnet resource id, a free static private IP inside it, and an SSH public key for break-glass access.
The two properties that matter
Section titled “The two properties that matter”A Marketplace VM needs both an image reference and a matching purchase
plan. Azure refuses the create without the plan, and the plan is also what
puts the offer into the attested instance document DTM checks at startup, so a
VM built from the image without one is created successfully and then never
becomes ready:
var imagePublisher = 'hayami'var imageOffer = 'dtm'var imageSku = 'dtm-payg-v1'
resource vm 'Microsoft.Compute/virtualMachines@2023-09-01' = { name: vmName location: location plan: empty(imageResourceId) ? { name: imageSku publisher: imagePublisher product: imageOffer } : null properties: { storageProfile: { imageReference: empty(imageResourceId) ? { publisher: imagePublisher offer: imageOffer sku: imageSku version: imageVersion } : { id: imageResourceId } // ... osDisk, dataDisks ... } // ... osProfile with customData, networkProfile ... }}Both are gated on empty(imageResourceId) on purpose. If you point
imageResourceId at your own Shared Image Gallery copy, the plan must become
null: Azure rejects a purchase plan on an image that does not carry one,
and ARM omits the property entirely for a null value.
The samples
Section titled “The samples”Each is a self-contained directory: one main.bicep and the cloud-init.yaml
beside it, no modules and nothing else to fetch. Download both files into an
empty directory and deploy.
| Topology | What it deploys | Files |
|---|---|---|
| Single region | One node, the cluster primary, with its own Key Vault | main.bicep · cloud-init.yaml |
| Dual region | Two nodes, one per region, sharing one Key Vault | main.bicep · cloud-init.yaml |
| Add a node | One more node joining a cluster you already have | main.bicep · cloud-init.yaml |
mkdir dtm && cd dtmcurl -O https://docs.hayami.io/samples/bicep/single-region/main.bicepcurl -O https://docs.hayami.io/samples/bicep/single-region/cloud-init.yaml
az deployment group create \ --resource-group rg-dtm \ --template-file main.bicep \ --parameters existingSubnetResourceId=<subnet-resource-id> \ privateIPAddress=10.50.250.4 \ adminPasswordOrKey="$(cat ~/.ssh/id_ed25519.pub)"cloud-init.yaml is the node’s first-boot configuration. main.bicep loads it
with loadTextContent() at compile time, so it must sit beside the
template when you deploy or the build fails. It is the same payload the
Marketplace ARM deployment uses.
Preview any of them with az deployment group what-if before applying.
Dual region
Section titled “Dual region”Two nodes, one per region, sharing a single Key Vault in the primary region. Node 1 generates the cluster secrets and node 2 reads them, so both join one encrypted cluster.
Adding a node later
Section titled “Adding a node later”add-node references a cluster you already have (its subnet, its Key Vault,
its current node IPs) and creates only the new node, as a read-only secondary
that reads the shared secrets and joins over gossip.
What the samples create
Section titled “What the samples create”A node, and the things only that node needs:
- the VM, with a system-assigned managed identity and no public IP
- a dedicated Premium SSD data disk for
/var/lib/dtm, attached separately so replacing the VM leaves the database in place - a NIC with a static private IP, which becomes the stable resolver address
- an NSG on the NIC, so the sample never takes over the security group on a subnet you own
- a per-cluster Key Vault (single and dual region), and the role assignments the node needs
The admin password and the gossip encryption key are generated on the VM at first boot and written to Key Vault. Neither is ever a template parameter or a deployment artifact, so neither appears in your deployment history. Read the initial password with the command the sample outputs, then change it.
Each VM deployed this way bills the DTM software charge per VM-hour on top of the Azure compute, storage and networking it uses. The current rate is on the Azure Marketplace listing; reservations apply where the listing says they do.
Day two
Section titled “Day two”- Upgrades replace the VM and keep its private IP. An ARM or Bicep deployed cluster upgrades with the Lifecycle swap, never a Terraform targeted apply; see rolling upgrades.
- Scaling out is the add-node sample above, or adding nodes.
- Managing DNS moves to the Terraform provider or the API once the cluster is up: provider setup, REST API.
Verify the cluster
Section titled “Verify the cluster”curl -sk https://<node-ip>:8443/readyz -o /dev/null -w '%{http_code}\n'# 200 = ready to serve; 503 = still starting or joiningThen follow the quickstart to create your first zone.
Where next
Section titled “Where next”- Deploy a cluster with Terraform: the same samples for Terraform estates.
- Deploy from the Azure Marketplace: the wizard, and what it creates for you.
- Network access (NSG): which ports the cluster needs and from where.
Need a hand? Email [email protected].
Last validated: 2026-09-15