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:
-
An active Azure subscription
-
Terraform installed on your machine
-
An existing Azure Storage Account for Terraform state
-
An SSH key pair on your local system
To generate an SSH key (if not already available):
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
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)
variable "subscription_id" {description = "the subscription ID"sensitive = true}
Main Infrastructure Configuration (main.tf)
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.namelocation = data.azurerm_resource_group.linux-rg.locationaddress_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.namevirtual_network_name = azurerm_virtual_network.linux-vnet.nameaddress_prefixes = ["10.0.0.0/24"]}resource "azurerm_public_ip" "linux-pip" {name = "linux-pip"resource_group_name = data.azurerm_resource_group.linux-rg.namelocation = data.azurerm_resource_group.linux-rg.locationallocation_method = "Static"}resource "azurerm_network_interface" "linux-nic" {name = "linux-nic"location = data.azurerm_resource_group.linux-rg.locationresource_group_name = data.azurerm_resource_group.linux-rg.nameip_configuration {name = "internal"private_ip_address_allocation = "Dynamic"subnet_id = azurerm_subnet.linux-subnet.idpublic_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.locationresource_group_name = data.azurerm_resource_group.linux-rg.namesecurity_rule {name = "SSH"priority = 1001direction = "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.idnetwork_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.namelocation = data.azurerm_resource_group.linux-rg.locationsize = "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"}}
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
terraform init
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.