Terraform Basics — Infrastructure as Code

BeginnerTopic45 min5 min read5 Jan 2025Terraform / IaC

Learn Terraform fundamentals — HCL syntax, providers, resources, variables, outputs, and the init/plan/apply/destroy workflow.

What you'll learn

  • Understand what Terraform is and why it's used
  • Write basic HCL configurations with resources, variables, and outputs
  • Use the core Terraform workflow: init, plan, apply, destroy
  • Understand Terraform state and why it matters
  • Use data sources to reference existing infrastructure

Relevant for certifications

TA-003

What is Terraform?

Terraform is an open-source Infrastructure as Code (IaC) tool by HashiCorp. It lets you define infrastructure in declarative HCL (HashiCorp Configuration Language) and manage it across any cloud provider.

You write code → Terraform plans changes → Terraform applies changes → Infrastructure exists

Benefits over clicking in the console:

  • Reproducible — run the same code, get the same infrastructure
  • Version controlled — track infrastructure changes in Git
  • Idempotent — run apply multiple times safely
  • Multi-cloud — one tool for Azure, AWS, GCP, Kubernetes, and 1000+ providers

Core Terraform Workflow

terraform init     # Download providers, initialise backend
terraform plan     # Show what WILL change (dry run)
terraform apply    # Apply changes (creates/modifies/destroys)
terraform destroy  # Destroy all managed resources

Tip

Always run terraform plan before apply. Review the output carefully — a + means create, ~ means modify, - means destroy.

HCL Syntax Fundamentals

Providers

A provider is a plugin that knows how to talk to a specific API (Azure, AWS, etc.):

# versions.tf
terraform {
  required_version = ">= 1.5"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

Resources

A resource is the main building block — it represents a piece of infrastructure:

# main.tf
resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-prod-uksouth"
  location = "UK South"

  tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
  }
}

resource "azurerm_storage_account" "store" {
  name                     = "stmyappprod001"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

Notice the reference: azurerm_resource_group.main.name — Terraform resolves this dependency automatically.

Variables

Variables make configurations reusable:

# variables.tf
variable "environment" {
  type        = string
  description = "Deployment environment (dev, test, prod)"
  default     = "dev"
}

variable "location" {
  type    = string
  default = "UK South"
}

variable "tags" {
  type    = map(string)
  default = {}
}

Use variables in resources:

resource "azurerm_resource_group" "main" {
  name     = "rg-myapp-${var.environment}"
  location = var.location
}

Provide values via:

terraform apply -var="environment=prod"
# Or via a .tfvars file:
terraform apply -var-file="prod.tfvars"

Outputs

Outputs expose values after apply — useful for consuming by other modules or scripts:

# outputs.tf
output "resource_group_id" {
  value       = azurerm_resource_group.main.id
  description = "Resource group ID"
}

output "storage_account_name" {
  value = azurerm_storage_account.store.name
}
terraform output                          # All outputs
terraform output storage_account_name    # Specific output

Data Sources

Data sources let you query existing infrastructure that isn't managed by this Terraform code:

# Reference an existing resource group
data "azurerm_resource_group" "existing" {
  name = "rg-network-prod"
}

# Use it in a resource
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-app"
  resource_group_name = data.azurerm_resource_group.existing.name
  location            = data.azurerm_resource_group.existing.location
  address_space       = ["10.0.0.0/16"]
}

Terraform State

The state file (terraform.tfstate) is Terraform's database of what it created. It maps your HCL code to real cloud resources.

terraform state list                          # List all resources in state
terraform state show azurerm_resource_group.main  # Show state for a resource
terraform state rm azurerm_resource_group.main    # Remove from state (but NOT from cloud)

Never edit state manually

The state file is JSON. Editing it by hand will corrupt your state. Use terraform state commands instead.

Remote State

For teams, always use remote state (not local terraform.tfstate):

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "stterraformstate001"
    container_name       = "tfstate"
    key                  = "prod/main.tfstate"
  }
}

Benefits:

  • Shared across the team
  • Locked during applies (prevents concurrent changes)
  • Not committed to Git (state contains secrets)

Complete Example: Azure Resource Group + Storage

# main.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "rg-${var.project}-${var.environment}"
  location = var.location
}

resource "azurerm_storage_account" "sa" {
  name                     = "st${var.project}${var.environment}"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  min_tls_version          = "TLS1_2"
}

# variables.tf
variable "project"     { default = "myapp" }
variable "environment" { default = "dev" }
variable "location"    { default = "UK South" }

# outputs.tf
output "rg_name" { value = azurerm_resource_group.rg.name }
output "sa_name" { value = azurerm_storage_account.sa.name }

Common Interview Questions

Q: What is Terraform state and why is it important? The state file maps Terraform code to real infrastructure. Without it, Terraform can't know what already exists and would try to recreate everything. It also stores metadata like resource dependencies.

Q: What is the difference between terraform plan and terraform apply? plan shows what changes WOULD be made (dry run — no infrastructure is changed). apply actually executes those changes. Always review plan output before applying.

Q: What is terraform import used for? terraform import brings existing cloud resources under Terraform management — it adds them to the state file. You still need to write the HCL resource block manually.

Q: How does Terraform handle resource dependencies? Terraform automatically builds a dependency graph from resource references (e.g., azurerm_resource_group.main.name). Explicit dependencies can be added with depends_on.

Common Mistakes

  • Committing state files to Git — state can contain secrets; use remote backends
  • Not pinning provider versions~> 3.0 means >= 3.0, < 4.0; without pinning, unexpected upgrades can break configs
  • Using terraform destroy in production — always double-check your state and workspace
  • One large main.tf — split into main.tf, variables.tf, outputs.tf, versions.tf from the start

What to Learn Next

  1. Terraform Modules — reusable infrastructure components
  2. Terraform Remote State and Backends — team workflows
  3. Terraform on Azure — Real Project — hands-on lab