<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yency Christopher</title>
    <description>The latest articles on DEV Community by Yency Christopher (@yency_christopher_4e54720).</description>
    <link>https://dev.to/yency_christopher_4e54720</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3654015%2Fe354c3f9-e209-4678-977f-06f8ea566cf1.png</url>
      <title>DEV Community: Yency Christopher</title>
      <link>https://dev.to/yency_christopher_4e54720</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yency_christopher_4e54720"/>
    <language>en</language>
    <item>
      <title>Understanding Terraform Variables the Right Way published: true description: A beginner-friendly guide</title>
      <dc:creator>Yency Christopher</dc:creator>
      <pubDate>Fri, 12 Dec 2025 01:31:38 +0000</pubDate>
      <link>https://dev.to/yency_christopher_4e54720/understanding-terraform-variables-the-right-way-published-true-description-a-beginner-friendly-3p5n</link>
      <guid>https://dev.to/yency_christopher_4e54720/understanding-terraform-variables-the-right-way-published-true-description-a-beginner-friendly-3p5n</guid>
      <description>&lt;h2&gt;
  
  
  variables.tf vs terraform.tfvars - What's the Difference?
&lt;/h2&gt;

&lt;p&gt;If you're new to Terraform, you've probably seen both** variables.tf** and &lt;strong&gt;terraform.tfvars&lt;/strong&gt; files and wondered: "Aren't they doing the same thing?"&lt;/p&gt;

&lt;p&gt;Spoiler alert: They're not! Let me break it down in the simplest way possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quick Answer:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;variables.tf&lt;/strong&gt; = The template (declares WHAT variables exist)&lt;br&gt;
&lt;strong&gt;terraform.tfvars&lt;/strong&gt; = The configuration (provides the ACTUAL values)&lt;/p&gt;
&lt;h2&gt;
  
  
  Think of it this way:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;variables.tf&lt;/strong&gt; asks the questions&lt;br&gt;
&lt;strong&gt;terraform.tfvars&lt;/strong&gt; gives the answers&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Analogy
&lt;/h2&gt;

&lt;p&gt;Imagine you're ordering a custom pizza:&lt;br&gt;
variables.tf (The Order Form)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "size" {
  description = "Pizza size"
  type        = string
  validation {
    condition     = contains(["small", "medium", "large"], var.size)
    error_message = "Size must be small, medium, or large"
  }
}

variable "toppings" {
  description = "Pizza toppings"
  type        = list(string)
  default     = ["cheese"]
}

variable "delivery_address" {
  description = "Where to deliver"
  type        = string
  # No default - this is required!
}

variable "phone" {
  description = "Contact number"
  type        = string
  default     = null  # Optional
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;terraform.tfvars (Your Filled Order)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;size             = "large"
toppings         = ["pepperoni", "mushrooms", "olives"]
delivery_address = "123 Main Street, Apt 4B"
phone            = "555-1234"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The restaurant keeps the same order form (&lt;strong&gt;variables.tf&lt;/strong&gt;) for everyone, but YOUR specific order (&lt;strong&gt;terraform.tfvars&lt;/strong&gt;) is unique to you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep Dive: variables.tf&lt;/strong&gt;&lt;br&gt;
Purpose: Declares the structure and rules for variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "subscription_id" {
  description = "Azure Subscription ID"
  type        = string
  sensitive   = true  # Won't show in logs
}

variable "vm_name" {
  description = "Name of the Virtual Machine"
  type        = string
  default     = "ubuntu-vm"  # Optional default value
}

variable "vm_size" {
  description = "VM size"
  type        = string
  default     = "Standard_B2s"

  validation {
    condition     = can(regex("^Standard_", var.vm_size))
    error_message = "VM size must start with 'Standard_'"
  }
}

variable "allowed_ips" {
  description = "Allowed IP addresses"
  type        = list(string)
  default     = []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it contains:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable name&lt;/li&gt;
&lt;li&gt;Type (string, number, bool, list, map, object)&lt;/li&gt;
&lt;li&gt;Description (documentation)&lt;/li&gt;
&lt;li&gt;Default value (optional)&lt;/li&gt;
&lt;li&gt;Validation rules (optional)&lt;/li&gt;
&lt;li&gt;Sensitive flag (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Think of it as&lt;/strong&gt;: The schema, the blueprint, the contract&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep Dive: terraform.tfvars&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; Provides actual values for your infrastructure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subscription_id = "12345678-1234-1234-1234-123456789abc"
vm_name         = "production-web-server"
vm_size         = "Standard_D4s_v3"
allowed_ips     = ["203.0.113.0/24", "198.51.100.0/24"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it contains:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just the values!&lt;/li&gt;
&lt;li&gt;No types, no descriptions, no validation&lt;/li&gt;
&lt;li&gt;Your specific configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Think of it as:&lt;/strong&gt; The actual data, your answers, the configuration&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Separation?&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;1. Reusability Across Environments&lt;/strong&gt;&lt;br&gt;
Keep the same &lt;strong&gt;variables.tf&lt;/strong&gt;, but use different values files:&lt;br&gt;
my-terraform-project/&lt;br&gt;
├── variables.tf          # Same for all environments&lt;br&gt;
├── main.tf               # Same for all environments&lt;br&gt;
├── dev.tfvars            # Dev-specific values&lt;br&gt;
├── staging.tfvars        # Staging-specific values&lt;br&gt;
└── prod.tfvars           # Production-specific values&lt;/p&gt;

&lt;p&gt;Deploy to different environments:&lt;br&gt;
terraform apply -var-file="dev.tfvars"      # Deploy to dev&lt;br&gt;
terraform apply -var-file="staging.tfvars"  # Deploy to staging&lt;br&gt;
terraform apply -var-file="prod.tfvars"     # Deploy to production&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Security &amp;amp; Git Management&lt;/strong&gt;&lt;br&gt;
 **.gitignore&lt;br&gt;
**terraform.tfvars      # Contains secrets - DON'T COMMIT&lt;br&gt;
*.auto.tfvars         # Contains secrets - DON'T COMMIT&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe to commit&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;variables.tf&lt;/strong&gt;               # Just the template&lt;br&gt;
&lt;strong&gt;terraform.tfvars.example&lt;/strong&gt;   # Example with fake values&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example file to commit:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;terraform.tfvars.example&lt;/strong&gt;&lt;br&gt;
subscription_id = "YOUR_SUBSCRIPTION_ID_HERE"&lt;br&gt;
vm_name         = "your-vm-name-here"&lt;br&gt;
vm_size         = "Standard_B2s"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Type Safety &amp;amp; Validation&lt;/strong&gt;&lt;br&gt;
variables.tf enforces rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "environment" {
  type = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod"
  }
}

variable "vm_count" {
  type = number
  validation {
    condition     = var.vm_count &amp;gt;= 1 &amp;amp;&amp;amp; var.vm_count &amp;lt;= 10
    error_message = "VM count must be between 1 and 10"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try to pass invalid values in terraform.tfvars, Terraform will catch it!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Complete Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzhy26c8nmtpyuf3v28x.png" rel="noopener noreferrer"&gt;Image description&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative Ways to Provide Values&lt;/strong&gt; &lt;br&gt;
You don't HAVE to use &lt;strong&gt;terraform.tfvars&lt;/strong&gt;. Here are your options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: terraform.tfvars (Recommended ✅)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
# Automatically reads terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Custom .tfvars file&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply -var-file="production.tfvars"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 3: Command-line flags&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply -var="vm_name=my-vm" -var="location=East US"
# Gets tedious with many variables!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 4: Environment variables&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export TF_VAR_vm_name="my-vm"
export TF_VAR_location="East US"
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 5: Interactive prompts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply
# Terraform prompts you:
# var.vm_name
#   Enter a value: _
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
✅ DO:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Always create variables.tf - it's your documentation&lt;/li&gt;
&lt;li&gt;Use terraform.tfvars for local development&lt;/li&gt;
&lt;li&gt;Create &lt;strong&gt;terraform.tfvars.example&lt;/strong&gt; with dummy values to commit&lt;/li&gt;
&lt;li&gt;Use separate .tfvars files per environment&lt;/li&gt;
&lt;li&gt;Add terraform.tfvars to*&lt;em&gt;.gitignore&lt;/em&gt;*&lt;/li&gt;
&lt;li&gt;Use descriptive variable names and descriptions&lt;/li&gt;
&lt;li&gt;Set reasonable defaults where appropriate&lt;/li&gt;
&lt;li&gt;Use validation rules for critical variables&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;❌ DON'T:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Commit terraform.tfvars with real secrets to Git&lt;/li&gt;
&lt;li&gt;Put actual values in variables.tf (except defaults)&lt;/li&gt;
&lt;li&gt;Use the same values across all environments&lt;/li&gt;
&lt;li&gt;Skip variable descriptions&lt;/li&gt;
&lt;li&gt;Forget to document required variables&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/artc07dbc3ldp6vstcff.png" rel="noopener noreferrer"&gt;Image description&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Practical Example
&lt;/h2&gt;

&lt;p&gt;💻&lt;br&gt;
Let's say you're deploying Azure VMs with Terraform:&lt;br&gt;
&lt;strong&gt;variables.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "subscription_id" {
  description = "Azure Subscription ID"
  type        = string
  sensitive   = true
}

variable "resource_group_name" {
  description = "Name of the resource group"
  type        = string
}

variable "location" {
  description = "Azure region"
  type        = string
  default     = "East US"
}

variable "vm_size" {
  description = "Size of the VM"
  type        = string
  default     = "Standard_B2s"
}

variable "environment" {
  description = "Environment (dev/staging/prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Must be dev, staging, or prod"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;dev.tfvars&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subscription_id     = "dev-subscription-id-here"
resource_group_name = "rg-myapp-dev"
location            = "East US"
vm_size             = "Standard_B2s"  # Smaller for dev
environment         = "dev"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;prod.tfvars&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subscription_id     = "prod-subscription-id-here"
resource_group_name = "rg-myapp-prod"
location            = "East US"
vm_size             = "Standard_D4s_v3"  # Larger for prod
environment         = "prod"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deploy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform apply -var-file="dev.tfvars"   # For development
terraform apply -var-file="prod.tfvars"  # For production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;🎓&lt;br&gt;
Remember the golden rule:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;variables.tf&lt;/strong&gt; = The questions (template/schema)&lt;br&gt;
&lt;strong&gt;terraform.tfvars&lt;/strong&gt; = The answers (your specific values)&lt;/p&gt;

&lt;p&gt;This separation gives you:&lt;/p&gt;

&lt;p&gt;✅ Reusable code across environments&lt;br&gt;
✅ Better security (secrets not in code)&lt;br&gt;
✅ Type safety and validation&lt;br&gt;
✅ Clear documentation&lt;br&gt;
✅ Team collaboration&lt;/p&gt;

&lt;p&gt;Now go forth and Terraform with confidence! 🚀&lt;/p&gt;

&lt;p&gt;Found this helpful? Give it a ❤️ and follow me for more DevOps and Cloud content!&lt;br&gt;
Questions? Drop them in the comments below! 👇&lt;/p&gt;

&lt;p&gt;Related Articles&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/developer/terraform/provision-infrastructure-using-azure-deployment-slots" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/developer/terraform/provision-infrastructure-using-azure-deployment-slots&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.codegenes.net/blog/best-practices-when-using-terraform-is-there-any-official-guide-to-it/" rel="noopener noreferrer"&gt;https://www.codegenes.net/blog/best-practices-when-using-terraform-is-there-any-official-guide-to-it/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>azure</category>
    </item>
    <item>
      <title>Building Azure Infrastructure with Terraform: A Complete Guide</title>
      <dc:creator>Yency Christopher</dc:creator>
      <pubDate>Tue, 09 Dec 2025 15:49:34 +0000</pubDate>
      <link>https://dev.to/yency_christopher_4e54720/building-azure-infrastructure-with-terraform-a-complete-guide-1o05</link>
      <guid>https://dev.to/yency_christopher_4e54720/building-azure-infrastructure-with-terraform-a-complete-guide-1o05</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Terraform lets you write configuration files that create cloud resources. Instead of clicking through Azure's portal, you define what you want in code, and Terraform builds it.&lt;br&gt;
This guide walks through deploying a Linux VM on Azure with proper networking and security. We'll start with basic resources, then reorganize everything into reusable modules that work across different projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What We'll Build&lt;/strong&gt;&lt;br&gt;
The Terraform code creates these Azure resources:&lt;br&gt;
Resource Group - Container for organizing resources&lt;br&gt;
Virtual Network (VNet) + Subnet - Private network for your resources&lt;br&gt;
Network Security Group (NSG) - Firewall rules&lt;br&gt;
Public IP Address - So you can connect from the internet&lt;br&gt;
Network Interface (NIC) - Connects the VM to the network&lt;br&gt;
Linux Virtual Machine - Ubuntu server with SSH access&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Variables&lt;/strong&gt;&lt;br&gt;
These variables let you customize the deployment without changing the main code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5ggygl6j4ay662niyys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5ggygl6j4ay662niyys.png" alt=" " width="681" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Setting my_ip_cidr to 0.0.0.0/0 lets anyone try to SSH in. Always use your specific IP with /32.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Infrastructure Code Explained&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resource Group
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-demo"
  location = var.location
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Resource Group is a container for all your Azure resources. Using var.location instead of hardcoding the region means you can deploy to different locations without editing the code.&lt;br&gt;
Tip: Use naming like rg-${var.project}-${var.env} to separate dev, staging, and prod environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Virtual Network (VNet)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VNet defines your private IP address space. The 10.0.0.0/16 range gives you 65,536 IP addresses.&lt;/p&gt;

&lt;p&gt;When you reference azurerm_resource_group.rg.location, Terraform knows it needs to create the Resource Group first. You don't have to specify the order—Terraform figures it out.&lt;/p&gt;

&lt;p&gt;Tip: Pick an address space that won't overlap with other VNets you might connect to later. Use variables for address spaces when building reusable modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Subnet&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subnets divide your VNet into smaller segments. The 10.0.1.0/24 prefix gives you 256 IP addresses.&lt;br&gt;
Note: We're attaching the NSG to the NIC in this guide, but you can also attach NSGs at the subnet level to protect all resources in that subnet at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Network Security Group (NSG)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NSG acts as a firewall. By itself, it's just a container the actual rules come next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. NSG Rule: SSH Access&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_network_security_rule" "ssh" {
  name                        = "Allow-SSH"
  priority                    = 1001
  direction                   = "Inbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "22"
  source_address_prefix       = var.my_ip_cidr
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.rg.name
  network_security_group_name = azurerm_network_security_group.nsg.name
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule allows SSH traffic (port 22) from your IP address.&lt;br&gt;
What the fields mean:&lt;/p&gt;

&lt;p&gt;priority (1001): Rules run from lowest to highest number (100-4096). Lower numbers go first.&lt;br&gt;
direction: Inbound controls incoming traffic; Outbound controls outgoing.&lt;br&gt;
source_address_prefix: var.my_ip_cidr restricts SSH to only your IP.&lt;/p&gt;

&lt;p&gt;For production: Don't expose SSH publicly. Use Azure Bastion or a jumpbox instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Public IP Address&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_public_ip" "pip" {
  name                = "pip-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Public IP lets you connect to your VM from the internet. &lt;strong&gt;Dynamic&lt;/strong&gt; allocation assigns an IP when the resource starts; &lt;strong&gt;Static&lt;/strong&gt; reserves a fixed IP.&lt;br&gt;
Production note: Avoid public IPs in production. Use private IPs with VPN, ExpressRoute, or Azure Bastion. If you need a fixed IP for DNS or firewall rules, use &lt;strong&gt;allocation_method = "Static"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Network Interface (NIC)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_network_interface" "nic" {
  name                = "nic-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

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

  network_security_group_id = azurerm_network_security_group.nsg.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NIC connects your VM to the network. This config:&lt;/p&gt;

&lt;p&gt;Attaches to the subnet with &lt;strong&gt;subnet_id&lt;/strong&gt;&lt;br&gt;
Associates the public IP with &lt;strong&gt;public_ip_address_id&lt;/strong&gt;&lt;br&gt;
Applies NSG rules with &lt;strong&gt;network_security_group_id&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;.id&lt;/strong&gt; attribute returns the unique Azure resource identifier. Terraform uses these to figure out what order to create things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Linux Virtual Machine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_linux_virtual_machine" "vm" {
  name                = "vm-demo"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  size                = var.vm_size
  admin_username      = var.admin_username
  network_interface_ids = [azurerm_network_interface.nic.id]

  admin_ssh_key {
    username   = var.admin_username
    public_key = file(var.ssh_public_key_path)
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "22_04-lts"
    version   = "latest"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key parts:&lt;/p&gt;

&lt;p&gt;admin_ssh_key: The &lt;strong&gt;file()&lt;/strong&gt; function reads your SSH public key and sets up authentication. Use the absolute path like &lt;strong&gt;/home/you/.ssh/id_rsa.pub&lt;/strong&gt;—Terraform doesn't expand ~.&lt;br&gt;
os_disk: &lt;strong&gt;Standard_LRS&lt;/strong&gt; is locally redundant storage. For better performance, use &lt;strong&gt;Premium_LRS&lt;/strong&gt; or &lt;strong&gt;StandardSSD_LRS&lt;/strong&gt;.&lt;br&gt;
source_image_reference: This pulls Ubuntu 22.04 LTS from Canonical. version = &lt;strong&gt;"latest"&lt;/strong&gt; always grabs the newest image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production tip&lt;/strong&gt;: Pin the image version to a specific build or use managed images so you get the same version every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Terraform Manages Dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Terraform builds a dependency graph based on references in your code. When you reference azurerm_resource_group.rg.name or .id, Terraform knows that the resource needs to exist first.&lt;/p&gt;

&lt;p&gt;Order of creation:&lt;br&gt;
&lt;strong&gt;Resource Group → VNet → Subnet → Public IP &amp;amp; NSG → NIC → VM&lt;/strong&gt;&lt;br&gt;
You don't need to manually specify the order Terraform figures it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration Files&lt;/strong&gt;&lt;br&gt;
variables.tf&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "location" {
  description = "Azure location"
  type        = string
  default     = "eastus"
}

variable "my_ip_cidr" {
  description = "Your public IP or CIDR to allow SSH (e.g. 203.0.113.4/32)"
  type        = string
  default     = "0.0.0.0/0"
}

variable "vm_size" {
  description = "Size of the VM"
  type        = string
  default     = "Standard_B1s"
}

variable "admin_username" {
  description = "Username for admin user"
  type        = string
  default     = "azureuser"
}

variable "ssh_public_key_path" {
  description = "Absolute path to SSH public key file"
  type        = string
  default     = "/home/you/.ssh/id_rsa.pub"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;terraform.tfvars (Example)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location = "eastus"
my_ip_cidr = "203.0.113.4/32"
vm_size = "Standard_B1s"
admin_username = "azureuser"
ssh_public_key_path = "/home/you/.ssh/id_rsa.pub"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security Warning: Never commit &lt;strong&gt;terraform.tfvars&lt;/strong&gt; with real secrets to version control. Add it to &lt;strong&gt;.gitignore&lt;/strong&gt; immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Your Terraform Code&lt;/strong&gt;&lt;br&gt;
Execute these commands in your project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize Terraform (downloads provider plugins)
terraform init

# Validate syntax and configuration
terraform validate

# Preview changes before applying
terraform plan -out=tfplan

# Apply the changes
terraform apply tfplan

# Or apply directly with auto-approval
terraform apply --auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform destroy --auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best Practices &amp;amp; Security&lt;/strong&gt;&lt;br&gt;
Security&lt;/p&gt;

&lt;p&gt;1.&lt;strong&gt;Lock Down SSH Access:&lt;/strong&gt; Set &lt;strong&gt;my_ip_cidr&lt;/strong&gt; to** /32*&lt;em&gt;. Run c&lt;/em&gt;&lt;em&gt;url ifconfig.me&lt;/em&gt;* to find your public IP.&lt;/p&gt;

&lt;p&gt;2.&lt;strong&gt;Use Azure Bastion:&lt;/strong&gt; For production, use Azure Bastion instead of public IPs for secure, browser-based access. &lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;Never Commit Secrets:&lt;/strong&gt; Add terraform.tfvars and *.tfstate to .gitignore. Use remote state storage for teams. &lt;/p&gt;

&lt;p&gt;Code Quality&lt;/p&gt;

&lt;p&gt;4.Use Variables: Parameterize locations, sizes, names, and tags so you can reuse code.&lt;br&gt;
5.Tag Everything: Add tags for cost tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tags = {
     owner       = "you"
     environment = var.env
     project     = "demo"
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Format and Validate: Run &lt;strong&gt;terraform fmt&lt;/strong&gt; and &lt;strong&gt;terraform validate&lt;/strong&gt; before committing. Set up pre-commit hooks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Team Collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remote State: Use Azure Storage backend with state locking to avoid conflicts.&lt;/li&gt;
&lt;li&gt;Least-Privilege Service Principals: For CI/CD, only grant necessary permissions (e.g., Resource Group Contributor, not Subscription Owner).
9.Use Modules: Split code into reusable modules (modules/network, modules/compute).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;10.Avoid Provisioners: Use cloud-init or Ansible instead of Terraform's provisioner blocks.&lt;br&gt;
11.Pin Provider Versions: Lock provider versions in your terraform block to prevent breaking changes.&lt;br&gt;
12.Monitor Resources: Enable Azure Monitor and diagnostics for production workloads.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps: Building Production-Ready Modules
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
Here's how to reorganize this into a scalable setup.&lt;br&gt;
&lt;strong&gt;Project Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7f67y41oxi86o23siz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7f67y41oxi86o23siz5.png" alt=" " width="449" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use This Structure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reusability:&lt;/strong&gt; Use modules across dev, staging, and production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation:&lt;/strong&gt; Network and compute teams can work independently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Test modules separately before combining them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning:&lt;/strong&gt; Version and publish stable modules to a registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;VS Code Integration&lt;/strong&gt;&lt;br&gt;
With VS Code tasks, you can run Terraform commands with one click:&lt;br&gt;
1.Open Command Palette (&lt;strong&gt;Ctrl+Shift+P&lt;/strong&gt;)&lt;br&gt;
2.Select "Tasks: Run Task"&lt;br&gt;
3.Choose: Init, Validate, Plan, Apply, or Destroy&lt;/p&gt;

&lt;p&gt;No more typing the same commands over and over.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Advanced Improvements&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once you're comfortable with the basics:&lt;br&gt;
1.Add Outputs: Export public IPs and resource IDs for other modules or documentation.&lt;br&gt;
2.Use Azure Key Vault: Store secrets securely and access them with managed identities.&lt;br&gt;
3.Enable Monitoring: Deploy Azure Monitor agents and set up Log Analytics.&lt;br&gt;
4.Multiple Environments: Use Terraform workspaces or separate tfvars files for dev/staging/prod.&lt;br&gt;
5.Set Up CI/CD: Run terraform plan on pull requests and terraform apply after approval.&lt;br&gt;
6.Implement Azure Bastion: Remove all public IPs and direct SSH access.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have the basics for managing Azure infrastructure with Terraform. This guide covered networking, security, and compute resources, organized into a maintainable structure.&lt;br&gt;
Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use variables to make code reusable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lock down security by restricting IP access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use modules for better organization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never commit secrets to version control&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use remote state for teams&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The modular setup will scale as you add more resources and environments. Start simple, automate early, and improve as you go.&lt;br&gt;
Ready to deploy? Update terraform.tfvars with your values and run terraform apply.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>infrastructureascode</category>
    </item>
  </channel>
</rss>
