In this tutorial, we will guide you on how to deploy an Azure Virtual Machine (VM) with Terraform. Terraform is an Infrastructure as Code (IaC) tool that allows you to define and provision infrastructure in a declarative manner. It is an open-source tool that enables you to define and manage your cloud infrastructure easily.
Before we start, ensure you have the following:
Create a new directory on your local machine and navigate to it using the command prompt or terminal.
Create a new file with the extension .tf
(e.g. main.tf
) in your directory. This file will contain the Terraform code to create the VM.
In your .tf
file, define the Azure provider as follows:
provider "azurerm" {
features {}
}
Create a resource group for your VM using the following code:
resource "azurerm_resource_group" "mtc-rg" {
name = "mtc-rg"
location = "Southeast Asia"
tags = {
environment = "dev"
}
}
Create a virtual network using the following code:
resource "azurerm_virtual_network" "mtc-vn" {
name = "mtc-network"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
address_space = ["10.0.0.0/16"]
tags = {
environment = "dev"
}
}
Create a subnet for your VM using the following code:
resource "azurerm_subnet" "mtc-subnet" {
name = "mtc-subnet"
resource_group_name = azurerm_resource_group.mtc-rg.name
virtual_network_name = azurerm_virtual_network.mtc-vn.name
address_prefixes = ["10.0.0.0/24"]
}
resource "azurerm_network_security_group" "mtc-sg" {
name = "mtc-sg"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
tags = {
environment = "dev"
}
}
resource "azurerm_network_security_rule" "mtc-dev-rule" {
name = "mtc-dev-rule"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.mtc-rg.name
network_security_group_name = azurerm_network_security_group.mtc-sg.name
}
resource "azurerm_network_security_rule" "mtc-dev-rule" {
name = "mtc-dev-rule"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.mtc-rg.name
network_security_group_name = azurerm_network_security_group.mtc-sg.name
}
resource "azurerm_subnet_network_security_group_association" "mtc-sga" {
subnet_id = azurerm_subnet.mtc-subnet.id
network_security_group_id = azurerm_network_security_group.mtc-sg.id
}
Create a public IP address for your VM using the following code:
resource "azurerm_public_ip" "mtc-ip" {
name = "mtc-ip"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
allocation_method = "Dynamic"
tags = {
environment = "dev"
}
}
Finally, create your VM using the following code:
resource "azurerm_network_interface" "mtc-nic" {
name = "mtc-nic"
location = azurerm_resource_group.mtc-rg.location
resource_group_name = azurerm_resource_group.mtc-rg.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.mtc-subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.mtc-ip.id
}
tags = {
environment = "dev"
}
}
Create customdata.tpl to execute script when VM is created. For example install Docker.
#!/bin/bash
sudo apt-get update -y &&
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -y &&
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
Generate SSH key for VM
ssh-keygen -t rsa
Create a virtual network using the following code:
resource "azurerm_linux_virtual_machine" "mtc-vm" {
name = "mtc-vm"
resource_group_name = azurerm_resource_group.mtc-rg.name
location = azurerm_resource_group.mtc-rg.location
size = "Standard_B1s"
admin_username = "adminuser"
network_interface_ids = [
azurerm_network_interface.mtc-nic.id,
]
custom_data = filebase64("customdata.tpl")
admin_ssh_key {
username = "adminuser"
public_key = file("~/.ssh/mtcazurekey.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
}
data "azurerm_public_ip" "ip-data" {
name = azurerm_public_ip.mtc-ip.name
resource_group_name = azurerm_resource_group.mtc-rg.name
depends_on = [azurerm_linux_virtual_machine.mtc-vm]
}
output "public_ip_address" {
value = "${azurerm_linux_virtual_machine.mtc-vm.name}: ${data.azurerm_public_ip.mtc-ip-data.ip_address}"
}
data "azurerm_subscription" "current" {
}
output "current_subscription_display_name" {
value = data.azurerm_subscription.current.display_name
}
data "azurerm_client_config" "current" {
}
output "account_id" {
value = data.azurerm_client_config.current.client_id
}
Terraform supports authenticating to Azure through a Service Principal or the Azure CLI. We recommend using a Service Principal when running in a shared environment (such as within a CI server/automation) - and authenticating via the Azure CLI when you’re running Terraform locally.
// Powershell
az ad sp create-for-rbac --name terraform --role Contributor --scopes /subscriptions/{your_subscription_id}
$env:ARM_SUBSCRIPTION_ID = "{your_subscription_id}"
$env:ARM_TENANT_ID = "{your_tenant_id}"
$env:ARM_CLIENT_ID = "{your_client_id}"
$env:ARM_CLIENT_SECRET = "{your_client_secret}"
Save your .tf
file and navigate to your directory using the command prompt or terminal. Run the following commands to deploy your VM:
terraform init
terraform plan
terraform apply
terraform destroy
After running the terrafrom plan
. Here is the output.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
<= read (data resources)
Terraform will perform the following actions:
# data.azurerm_public_ip.mtc-ip-data will be read during apply
# (depends on a resource or a module with changes pending)
<= data "azurerm_public_ip" "mtc-ip-data" {
+ allocation_method = (known after apply)
+ domain_name_label = (known after apply)
+ fqdn = (known after apply)
+ id = (known after apply)
+ idle_timeout_in_minutes = (known after apply)
+ ip_address = (known after apply)
+ ip_tags = (known after apply)
+ ip_version = (known after apply)
+ location = (known after apply)
+ name = "mtc-ip"
+ resource_group_name = "mtc-rg"
+ reverse_fqdn = (known after apply)
+ sku = (known after apply)
+ tags = (known after apply)
+ zones = (known after apply)
}
# azurerm_linux_virtual_machine.mtc-vm will be created
+ resource "azurerm_linux_virtual_machine" "mtc-vm" {
+ admin_username = "adminuser"
+ allow_extension_operations = true
+ computer_name = (known after apply)
+ custom_data = (sensitive value)
+ disable_password_authentication = true
+ extensions_time_budget = "PT1H30M"
+ id = (known after apply)
+ location = "southeastasia"
+ max_bid_price = -1
+ name = "mtc-vm"
+ network_interface_ids = (known after apply)
+ patch_mode = "ImageDefault"
+ platform_fault_domain = -1
+ priority = "Regular"
+ private_ip_address = (known after apply)
+ private_ip_addresses = (known after apply)
+ provision_vm_agent = true
+ public_ip_address = (known after apply)
+ public_ip_addresses = (known after apply)
+ resource_group_name = "mtc-rg"
+ size = "Standard_B1s"
+ virtual_machine_id = (known after apply)
+ admin_ssh_key {
+ public_key = <<-EOT
ssh-rsa *******************************
EOT
+ username = "adminuser"
}
+ os_disk {
+ caching = "ReadWrite"
+ disk_size_gb = (known after apply)
+ name = (known after apply)
+ storage_account_type = "Standard_LRS"
+ write_accelerator_enabled = false
}
+ source_image_reference {
+ offer = "UbuntuServer"
+ publisher = "Canonical"
+ sku = "18.04-LTS"
+ version = "latest"
}
}
# azurerm_network_interface.mtc-nic will be created
+ resource "azurerm_network_interface" "mtc-nic" {
+ applied_dns_servers = (known after apply)
+ dns_servers = (known after apply)
+ enable_accelerated_networking = false
+ enable_ip_forwarding = false
+ id = (known after apply)
+ internal_dns_name_label = (known after apply)
+ internal_domain_name_suffix = (known after apply)
+ location = "southeastasia"
+ mac_address = (known after apply)
+ name = "mtc-nic"
+ private_ip_address = (known after apply)
+ private_ip_addresses = (known after apply)
+ resource_group_name = "mtc-rg"
+ tags = {
+ "environment" = "dev"
}
+ virtual_machine_id = (known after apply)
+ ip_configuration {
+ gateway_load_balancer_frontend_ip_configuration_id = (known after apply)
+ name = "internal"
+ primary = (known after apply)
+ private_ip_address = (known after apply)
+ private_ip_address_allocation = "Dynamic"
+ private_ip_address_version = "IPv4"
+ public_ip_address_id = (known after apply)
+ subnet_id = (known after apply)
}
}
# azurerm_network_security_group.mtc-sg will be created
+ resource "azurerm_network_security_group" "mtc-sg" {
+ id = (known after apply)
+ location = "southeastasia"
+ name = "mtc-sg"
+ resource_group_name = "mtc-rg"
+ security_rule = (known after apply)
+ tags = {
+ "environment" = "dev"
}
}
# azurerm_network_security_rule.mtc-dev-rule will be created
+ resource "azurerm_network_security_rule" "mtc-dev-rule" {
+ access = "Allow"
+ destination_address_prefix = "*"
+ destination_port_range = "*"
+ direction = "Inbound"
+ id = (known after apply)
+ name = "mtc-dev-rule"
+ network_security_group_name = "mtc-sg"
+ priority = 100
+ protocol = "*"
+ resource_group_name = "mtc-rg"
+ source_address_prefix = "*"
+ source_port_range = "*"
}
# azurerm_public_ip.mtc-ip will be created
+ resource "azurerm_public_ip" "mtc-ip" {
+ allocation_method = "Dynamic"
+ fqdn = (known after apply)
+ id = (known after apply)
+ idle_timeout_in_minutes = 4
+ ip_address = (known after apply)
+ ip_version = "IPv4"
+ location = "southeastasia"
+ name = "mtc-ip"
+ resource_group_name = "mtc-rg"
+ sku = "Basic"
+ sku_tier = "Regional"
+ tags = {
+ "environment" = "dev"
}
}
# azurerm_resource_group.mtc-rg will be created
+ resource "azurerm_resource_group" "mtc-rg" {
+ id = (known after apply)
+ location = "southeastasia"
+ name = "mtc-rg"
+ tags = {
+ "environment" = "dev"
}
}
# azurerm_subnet.mtc-subnet will be created
+ resource "azurerm_subnet" "mtc-subnet" {
+ address_prefixes = [
+ "10.0.0.0/24",
]
+ enforce_private_link_endpoint_network_policies = false
+ enforce_private_link_service_network_policies = false
+ id = (known after apply)
+ name = "mtc-subnet"
+ resource_group_name = "mtc-rg"
+ virtual_network_name = "mtc-network"
}
# azurerm_subnet_network_security_group_association.mtc-sga will be created
+ resource "azurerm_subnet_network_security_group_association" "mtc-sga" {
+ id = (known after apply)
+ network_security_group_id = (known after apply)
+ subnet_id = (known after apply)
}
# azurerm_virtual_network.mtc-vn will be created
+ resource "azurerm_virtual_network" "mtc-vn" {
+ address_space = [
+ "10.0.0.0/16",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "southeastasia"
+ name = "mtc-network"
+ resource_group_name = "mtc-rg"
+ subnet = (known after apply)
+ tags = {
+ "environment" = "dev"
}
}
Plan: 9 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ account_id = "*******************************"
+ current_subscription_display_name = "dev-sub"
+ public_ip_address = (known after apply)
The terraform apply
command will prompt you to confirm the deployment. Type yes
and press enter to proceed.
Run terraform destroy
to destroy the environment.
Congratulations! You have successfully deployed an Azure VM using Terraform.
In this tutorial, we have shown you how to deploy an Azure VM using Terraform. Terraform simplifies the process of deploying infrastructure by allowing you to define and manage your resources in a declarative manner. With Terraform, you can easily create, update, and delete resources as needed. We hope you found this tutorial helpful. If you have any questions or comments, feel free to leave them below.
Copyright (c) 2024