DEV Community

Smallsun2025
Smallsun2025

Posted on

Deploy a Linux VM on Azure with a Custom Startup Script using Terraform

πŸ—‚ Project Structure

The Terraform project consists of the following files:

azure-linuxvm-nsg-nginx-demo/
β”œβ”€β”€ main.tf # Main infrastructure definitions
β”œβ”€β”€ variables.tf # Input variable declarations
β”œβ”€β”€ terraform.tfvars # Variable values (location, credentials, etc.)
β”œβ”€β”€ outputs.tf # Outputs like public IP
β”œβ”€β”€ startup-script.sh # Bash script to install and enable nginx

Each file serves a specific purpose to keep the project modular, secure, and easy to manage.


πŸ”§ Terraform Configuration Breakdown

This section explains each component used in the deployment.


1. Resource Group

resource "azurerm_resource_group" "rg" {
  name     = var.resource_group_name
  location = var.location
}
πŸ’‘ Creates a container to hold all related Azure resources.

2. Virtual Network and Subnet

resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-demo"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = "subnet-demo"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]
}
πŸ’‘ Defines the internal network where the VM will reside.

3. Public IP Address

resource "azurerm_public_ip" "public_ip" {
  name                = "public-ip-demo"
  location            = var.location
  resource_group_name = var.resource_group_name
  allocation_method   = "Dynamic"
}
πŸ’‘ Allows the VM to be accessible from the internet.

4. Network Security Group (NSG)
resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-demo"
  location            = var.location
  resource_group_name = var.resource_group_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 = "*"
  }

  security_rule {
    name                       = "HTTP"
    priority                   = 1002
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}
πŸ’‘ Allows traffic to ports 22 (SSH) and 80 (HTTP) from any IP.

5. Network Interface + NSG Binding
resource "azurerm_network_interface" "nic" {
  name                = "nic-demo"
  location            = var.location
  resource_group_name = var.resource_group_name

  ip_configuration {
    name                          = "ipconfig"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.public_ip.id
  }

  network_security_group_id = azurerm_network_security_group.nsg.id
}
πŸ’‘ Binds the NIC to the subnet and NSG. Associates a public IP.

---

### 6. Startup Script: `startup-script.sh`

Enter fullscreen mode Exit fullscreen mode


bash

!/bin/bash

sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl start nginx
sudo systemctl enable nginx

πŸ’‘ This script installs and starts nginx automatically when the VM is created.

  1. Linux Virtual Machine resource "azurerm_linux_virtual_machine" "vm" { name = var.vm_name location = var.location resource_group_name = var.resource_group_name network_interface_ids = [azurerm_network_interface.nic.id] size = "Standard_B1s" admin_username = var.admin_username admin_password = var.admin_password disable_password_authentication = false

source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
name = "osdisk-demo"
}

computer_name = "demo-vm"
provision_vm_agent = true
custom_data = base64encode(file("startup-script.sh"))
}
πŸ’‘ This creates a Linux VM and runs the startup script to configure the web server.

  1. Outputs output "resource_group_name" { value = azurerm_resource_group.rg.name }

output "public_ip_address" {
value = azurerm_public_ip.public_ip.ip_address
}
πŸ’‘ After deployment, these outputs help you identify the resource group and access the VM.


βœ… Conclusion

With just a few Terraform files, we successfully deployed:

  • A secure Linux Virtual Machine on Azure
  • A fully working nginx web server
  • Automated provisioning via a startup script
  • Public access through a dynamically assigned IP address

This approach demonstrates the power of Infrastructure as Code (IaC) and how easily repeatable environments can be created using Terraform.


πŸ“¦ GitHub Repository

You can find the full source code for this project here:

πŸ‘‰ https://github.com/Smallsun2025/azure-linuxvm-nsg-nginx-demo

⭐️ Feel free to star, fork, or leave feedback!


🧠 Coming Next

In future posts, I’ll walk through:

  • Creating Azure VM images with Packer + Terraform
  • Setting up 3-tier architecture with Load Balancer
  • Using Terraform Cloud for remote state & collaboration

Follow for more Azure automation tips!

Top comments (0)