 
								your123 - stock.adobe.com
Automating domain joins for Azure VMs with Terraform
Streamline the provisioning of Windows VMs in Azure, then securely join them to the on-premises AD domain using Terraform in combination with Azure Key Vault.
As infrastructure grows, so does complexity. Automation with tools such as Terraform gives IT teams a way to scale more efficiently without adding to operational overhead.
The goal of any automated provisioning process, such as using Terraform to configure a Windows Server in Azure, is to automate as many of the configuration tasks as possible to streamline infrastructure setup. In an AD environment, joining these new servers to the domain is a crucial step to enable management with centralized tools, enforce security and enable governance.
This article will explain how to use Terraform to not only provision a Windows VM in Azure but also automatically join it to an on-premises AD domain to make sure the VM is ready for use immediately. This automation process also includes using Azure Key Vault to perform the server configuration securely.
How domain-join automation with Terraform works
The domain-join process is often handled with a configuration management tool, such as Ansible, Puppet, Saltstack or Chef. But, as a popular infrastructure-as-code (IaC) tool, Terraform enables you to describe the infrastructure in a declarative format and then deploy it with minimal manual effort. This raises the question: Should your configuration management tool handle domain joins, or should your IaC tool take care of it during provisioning?
Although Terraform isn’t designed for detailed configuration jobs, such as a domain join, it can use Azure VM extensions for this purpose. Specifically, JsonADDomainExtension can be used to automatically join the VM to the AD domain after deployment. This approach lets you use just Terraform rather than multiple tools to both deploy the VM and execute the domain join to simplify the process and maintain consistency across environments.
Provisioning a Windows VM in Azure with Terraform
Before configuring the Azure VM extension for the domain join, let's provision the VM with Terraform. Start by defining the Azure provider.
provider "azurerm" {
  features {}
}Next, attach the VM to a subnet with DNS settings that point to the domain controller and the proper open ports. For this tutorial, we'll assume the configured subnet exists, so we'll import it with a data block.
data "azurerm_subnet" "example" {
  name                 = "Subnet Name"
  virtual_network_name = "vNet Name"
  resource_group_name  = var.resource_group_name
}Next, create a network interface card (NIC) and connect it to the Azure subnet, then deploy a Windows Server VM in Azure. The code attaches the VM to the NIC so it can communicate on the private network with the domain controller.
resource "azurerm_network_interface" "example" {
  name                = "${var.name}-nic"
  location            = var.location
  resource_group_name = var.resource_group_name
  ip_configuration {
    name                          = "internal"
    subnet_id                     = data.azurerm_subnet.example.id
    private_ip_address_allocation = "Dynamic"
  }
}
resource "azurerm_windows_virtual_machine" "example" {
  name                = var.name
  resource_group_name = var.resource_group_name
  location            = var.location
  size                = "Standard_F2"
  admin_username      = "adminuser"
  admin_password      = var.admin_password
  network_interface_ids = [
    azurerm_network_interface.example.id,
  ]
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2025-Datacenter"
    version   = "latest"
  }
}The NIC takes its name from the name of the VM. This naming convention clarifies the association between the NIC and its VM and reduces the number of variables in the Terraform configuration.
Next, use the following Terraform code to add the Azure VM extension, JsonADDomainExtension, which automatically joins a Windows VM in Azure to the AD domain:
resource "azurerm_virtual_machine_extension" "example" {
  name                 = "DomainJoin"
  virtual_machine_id   = azurerm_windows_virtual_machine.example.id
  publisher            = "Microsoft.Compute"
  type                 = "JsonADDomainExtension"
  type_handler_version = "1.3"
  settings = <<SETTINGS
    {
      "Name": "domain.com",
      "OUPath": "OU=Servers,DC=domain,DC=com",
      "User": "domain\${var.domain_join_user}",
      "Restart": "true",
      "Options": "3"
    }
  SETTINGS
  protected_settings = <<PROTECTED_SETTINGS
    {
      "Password": "${var.domain_join_password}"
    }
  PROTECTED_SETTINGS
}This extension is associated with the VM through the virtual_machine_id property, which references the deployed VM ID.
The domain-related settings are in the settings and protected_settings sections. For your domain, you must replace or use variables for the following settings:
- domain.com: Replace with the AD domain name.
- OU=Servers,DC=domain,DC=com: Replace with the organizational unit (OU) where you want to add the server.
- On the User line, replace domain with the short name of the login domain.
Using Azure Key Vault to securely handle domain-join credentials
It’s important to understand that the previous configuration requires passing the password as a variable. This value needs to either be in a .tfvars file or passed to the Terraform executable as a parameter.
There are several ways to accomplish both approaches, but, when working in Azure, the recommendation is to manage the password in Azure Key Vault. Assuming the identity used to deploy the configuration has read access to secrets in the Azure Key Vault, retrieve the password using Terraform data blocks and pass it to the domain-join extension.
data "azurerm_key_vault" "example" {
  name                = "example-keyvault"
  resource_group_name = var.resource_group_name
}
data "azurerm_key_vault_secret" "domain-join-pw" {
  name         = "domain-join-pw"
  key_vault_id = data.azurerm_key_vault.example.id
}
resource "azurerm_virtual_machine_extension" "example" {
  name                 = "DomainJoin"
  virtual_machine_id   = azurerm_windows_virtual_machine.example.id
  publisher            = "Microsoft.Compute"
  type                 = "JsonADDomainExtension"
  type_handler_version = "1.3"
  settings = <<SETTINGS
    {
      "Name": "domain.com",
      "OUPath": "OU=Servers,DC=domain,DC=com",
      "User": "domain\${var.domain_join_user}",
      "Restart": "true",
      "Options": "3"
    }
  SETTINGS
  protected_settings = <<PROTECTED_SETTINGS
    {
      "Password": "${data.azurerm_key_vault_secret.domain-join-pw.value}"|
    }
  PROTECTED_SETTINGS
}In the script, the extension references a secret called domain-join-pw in the Azure Key Vault named example-keyvault. The code securely retrieves and passes the password to the VM extension using the protected_settings block to ensure encryption during deployment.
How to test the Azure VM domain join
After running the Terraform configuration and provisioning the VM, verify its domain-join status in the Azure portal.
- Navigate to the VM.
- Expand Settings.
- Click on Extensions + applications.
 
  Click on the DomainJoin extension to open a modal window to review its status.
If you don’t see the Join completed message, then the recommendation is to connect to the VM and troubleshoot the domain-join failure using standard Windows diagnostics. Once you have determined the root cause, you might need to adjust DNS or network-security groups if you have network-security rules in place.
Scaling Windows VM deployments with domain join
It's simple enough to deploy multiple domain-joined Windows VMs using Terraform modules.
The configuration used in earlier examples can be converted into a Terraform module by adding parameters to reduce hardcoded values. After defining the module, you create a new VM by calling the module. The following example shows how to call the module.
module "windowsvm-01" {
  source               = "./modules/windowsvm"
  name                 = "windowsvm-01"
  resource_group_name  = "rg-01"
  location             = "East US"
  admin_pw_secret_name = "admin-pw"
  domain_join_user     = "domainuser"
  size                 = "Standard_F2"
}To create another VM, copy the module block and update the appropriate values, such as the VM name, resource group or size.
Enlist automation to scale infrastructure securely
Automating Windows VM deployment and domain join not only saves administrators significant time and effort but also ensures consistency with configurations across the domain. The combination of Terraform modules and Azure Key Vault for managing sensitive credentials makes it possible to securely scale this process. This approach reduces the manual work that can introduce errors and helps enforce IaC best practices.
Anthony Howell is an IT strategist with extensive experience in infrastructure and automation technologies. His expertise includes PowerShell, DevOps, cloud computing, and working in both Windows and Linux environments.
 
					 
					 
									 
					 
									 
					 
					