Wednesday, 4 February 2026

Create a Linux Virtual Machine in Azure Using Terraform

Create a Linux Virtual Machine in Azure Using Terraform
vmcreation



Introduction

Infrastructure as Code (IaC) helps automate cloud resource provisioning in a reliable and repeatable way. Terraform is one of the most widely used IaC tools for managing cloud infrastructure.

In this blog, you will learn how to create a Linux Virtual Machine in Microsoft Azure using Terraform, configure a remote backend for state management, and connect to the VM using SSH.

By the end of this guide, you will be able to:

1. Configure Azure provider in Terraform

2. Store Terraform state in Azure Storage Account

3. Create networking resources (VNet, Subnet, NSG, Public IP)

4. Deploy a Linux Virtual Machine

5. Connect securely to the machine

Prerequisite:

Before starting, ensure that you have:

  1. An active Azure subscription

  2. Terraform installed on your machine

  3. An existing Azure Storage Account for Terraform state

  4. An SSH key pair on your local system

To generate an SSH key (if not already available):

ssh-keygen -t rsa -b 4096

Project Structure

Create a new project directory and organize files as follows:

Considering you have the storage account created, for  this demo i have already created resource group and in same resource group i have created the storage account and container. Here my storage account name is linuxvmsa and container name is terraform.

terraform-linux-vm/

 ├── main.tf

 ├── provider.tf

 ├── variables.tf

 ├── outputs.tf

 ├── dev.tfvars

 └── .gitignore

please follow the link for more on the standard structure of terraform files : link 

Creating Terraform Files

Create Files

On Linux/macOS:

touch main.tf variable.tf provider.tf

On Windows (PowerShell):

New-Item  -file main.tf provider.tf  variables.tf outputs.tf

Provider Configuration (provider.tf)

First, lets configure the provider. In Terraform, a provider is a plugin that acts as the bridge between terraform and the target platform. In today's demo the target platform is azure.

So in the provider.tf file, update below code:


terraform {
required_providers {

azurerm={
source = "hashicorp/azurerm"
version = "4.58.0"
}
}
}

provider "azurerm" {

features {

}
subscription_id = var.subscription_id
}


Here, subscription_id is the subscription ID of your account with Azure. azurerm is provider we will use for the demo. You can define the subscription ID as a secret in variable group of azure devops pipeline or in case of local have it defined in the .tfvars file but do not add this file in the version control. As we testing locally we will create .tfvars file and have the subscription mentioned there.
simply mention Id in file, for ex:
subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"


In-order to have the state file in remote backend, have the below backend block. you can paste this in terraform block we given in provider.tf file
backend "azurerm" {
container_name = "terraform"
resource_group_name = "Linuxvm-rg"
key = "terrform.tfstate"
storage_account_name = "linuxvmsa"
}
 

Why Use a Remote Backend?

Using a remote backend:

1. Prevents state file loss

2. Enables team collaboration

3. Supports state locking

4. Improves security

 

Variables Definition (variables.tf)

Write the subscription ID as variable in variable.tf, below is the variable block format:
variable "subscription_id" {
description = "the subscription ID"
sensitive = true
}

Main Infrastructure Configuration (main.tf)

Lets write and create resources now in main.tf file 

You can take below file as main resources:
data "azurerm_resource_group" "linux-rg" {
name = "Linuxvm-rg"
}

resource "azurerm_virtual_network" "linux-vnet" {
name = "linux-vnet"
resource_group_name = data.azurerm_resource_group.linux-rg.name
location = data.azurerm_resource_group.linux-rg.location
address_space = ["10.0.0.0/16"]
tags = { "linuxvm" = "test" }
}

resource "azurerm_subnet" "linux-subnet" {
name = "linux-subnet"
resource_group_name = data.azurerm_resource_group.linux-rg.name
virtual_network_name = azurerm_virtual_network.linux-vnet.name
address_prefixes = ["10.0.0.0/24"]

}
resource "azurerm_public_ip" "linux-pip" {
name = "linux-pip"
resource_group_name = data.azurerm_resource_group.linux-rg.name
location = data.azurerm_resource_group.linux-rg.location
allocation_method = "Static"
}

resource "azurerm_network_interface" "linux-nic" {
name = "linux-nic"
location = data.azurerm_resource_group.linux-rg.location
resource_group_name = data.azurerm_resource_group.linux-rg.name
ip_configuration {
name = "internal"
private_ip_address_allocation = "Dynamic"
subnet_id = azurerm_subnet.linux-subnet.id
public_ip_address_id = azurerm_public_ip.linux-pip.id
}

}

resource "azurerm_network_security_group" "linux-nsg" {
name = "linux-nsg"
location = data.azurerm_resource_group.linux-rg.location
resource_group_name = data.azurerm_resource_group.linux-rg.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}

resource "azurerm_network_interface_security_group_association" "linux-association" {
network_interface_id = azurerm_network_interface.linux-nic.id
network_security_group_id = azurerm_network_security_group.linux-nsg.id

}

resource "azurerm_linux_virtual_machine" "linux-vm" {
name = "linux01"
resource_group_name = data.azurerm_resource_group.linux-rg.name
location = data.azurerm_resource_group.linux-rg.location
size = "Standard_B2ms"
network_interface_ids = [azurerm_network_interface.linux-nic.id]
source_image_reference {
publisher = "canonical"
offer = "ubuntu-24_04-lts"
sku = "ubuntu-pro-gen1"
version = "latest"
}
admin_username = "azureuser"
admin_ssh_key {
public_key = file("/Users/parsh/.ssh/id_rsa.pub")
username = "azureuser"
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

}

data block: to reference the existing infrastructure in your current infrastructure

As per the design shared at the top resources created. Public IP created to connect the vm.

Output Values (outputs.tf)

In output.tf write below piece of code to have public ip on terminal after apply.
output "public_ip" {
  value = azurerm_public_ip.linux-pip.ip_address
}

 


Running Terraform Commands

1. initialize the directory with terraform 
terraform init
2. format the terraform code
terraform fmt

3. validate the code before apply

terraform validate 

4. terrafrom plan (dry run)

terraform plan -var-file="dev.tfvars"

5. run the files

terraform apply  -var-file="dev.tfvars" -auto-approve 

6. after your test done perform destroy

terraform destroy -var-file="dev.tfvars"

Connecting to the Virtual Machine

After deployment, Terraform will display the public IP. 

ssh <user>@<publicip>

Conclusion

In this blog, we learned how to:

1. Configure Terraform with Azure

2. Use Azure Storage as a remote backend

3. Create networking and VM resources

4. Connect to Linux VM using SSH

5. Apply security best practices

This approach enables scalable, repeatable, and secure infrastructure deployment using Terraform.

Author Details

Hi, I'm Prashant — a full-time software engineer with a passion for automation, DevOps, and sharing what I learn. I started Py-Bucket to document my journey through tools like Docker, Kubernetes, Azure DevOps, and PowerShell scripting — and to help others navigate the same path. When I’m not coding or writing, I’m experimenting with side projects, exploring productivity hacks, or learning how to build passive income streams online. This blog is my sandbox — and you're welcome to explore it with me. Get in touch or follow me for future updates!

About Me

About the Author

Author

Hi, I'm Prashant — a full-time software engineer with a passion for automation, DevOps, and sharing what I learn. I started Py-Bucket to document my journey through tools like Docker, Kubernetes, Azure DevOps, and PowerShell scripting — and to help others navigate the same path.

When I’m not coding or writing, I’m experimenting with side projects, exploring productivity hacks, or learning how to build passive income streams online. This blog is my sandbox — and you're welcome to explore it with me.

Get in touch or follow me for future updates!