Skip to content

alt text

Automated Resource Deployment via Terraform

Overview of Terraform

Terraform is an open‑source tool for building, changing and versioning infrastructure safely and efficiently. It uses a declarative configuration language (HashiCorp Configuration Language, HCL) to describe the desired state of your infrastructure. When applied, Terraform generates an execution plan showing what it will do to reach that state and then executes the plan to provision, update or destroy resources across a variety of cloud providers.

Key features of Terraform:

  • Declarative syntax
  • Define “what” you want, not “how” to get there.
  • Provider ecosystem
  • First‑class support for Azure through the AzureRM provider.
  • State management
  • Keeps track of real‑world resources in a state file, enabling powerful plans and diffs.
  • Modules
  • Reusable, composable units of configuration that promote DRY and consistency.
  • Workspaces
  • Handle multiple environments (for example dev, test, prod) in a single configuration.

General Best Practices

  • Use version control
  • Store all Terraform code in Git or another VCS. Review changes via pull requests to catch mistakes early.
  • Lock provider and Terraform Versions
  • Specify exact versions to avoid unexpected changes when new versions are released:
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=4.50.0"
    }
  }
}
  • Use modules for reuse and clarity
  • Break down your infrastructure into logical modules, each handling one responsibility (for example resource group, service plan, web app, private endpoint).
  • Group variables and outputs
  • Define all variables in variables.tf files with clear descriptions, types and defaults. Expose needed values via outputs.tf.
  • Manage secrets securely
  • Do not commit sensitive values in Git. Use Azure Key Vault or environment variables and mark variables as sensitive = true.
  • Tag resources consistently
  • Apply tags such as environment, managed_by, and cost_center to all resources for governance and cost tracking.
  • Use linting and formatting tools**
  • Run terraform fmt and terraform validate in CI pipelines to enforce style and detect errors early.

Terraform Project Structure

Below is the recommended layout for a Terraform project deploying Azure resources:

terraform/
├── main.tf              root module: providers + module calls
├── variables.tf         all your var definitions
├── providers.tf         provider + terraform blocks (optional split)
├── terraform.tfvars     your actual var values
└── modules/
    ├── resource_group/
       ├── main.tf
       ├── variables.tf
       └── outputs.tf
    ├── service_plan/
       ├── main.tf
       ├── variables.tf
       └── outputs.tf
    ├── linux_web_app/
       ├── main.tf
       ├── variables.tf
       └── outputs.tf
    └── private_endpoint/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Root Module

main.tf

  • Purpose
  • Orchestrates module calls and defines resource dependencies.

  • Best Practices

  • Keep it focused on wiring up modules.
  • Make use of depends_on only when implicit dependencies are insufficient
module "rg" {
  source     = "./modules/resource_group"
  name       = var.rg_name
  location   = var.location
}

module "plan" {
  source             = "./modules/service_plan"
  rg_name            = module.rg.name
  app_service_tier   = var.app_service_tier
}

module "webapp" {
  source       = "./modules/linux_web_app"
  rg_name      = module.rg.name
  service_plan = module.plan.name
  app_name     = var.app_name
}

module "endpoint" {
  source            = "./modules/private_endpoint"
  rg_name           = module.rg.name
  virtual_network   = var.virtual_network
  subnet_name       = var.subnet_name
  webapp_resource_id = module.webapp.id
}

variables.tf

  • Purpose
  • Declare all inputs for the root module.

  • Best Practices

  • Provide description, type, and sensible defaults
  • Mark truly optional settings with defaults; require mandatory ones without defaults.
variable "rg_name" {
  description = "Name of the resource group"
  type        = string
}

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

variable "app_name" {
  description = "Name for the Linux web app"
  type        = string
}

providers.hcl

  • Purpose
  • Configure the Terraform backend and Azure provider.

  • Best Practices

  • Split if you have multiple providers or to separate concerns.
terraform {
  required_version = ">= 1.5.0"
  backend "azurerm" {
    resource_group_name  = "tfstate-rg"
    storage_account_name = "tfstate"
    container_name       = "state"
    key                  = "prod.terraform.tfstate"
  }
}

provider "azurerm" {
  features {}
}

terraform.tfvars

  • Purpose
  • Store actual values for variables (do not commit secrets).

  • Best Practices

  • Add this file to .gitignore.
  • For secrets, consider using a separate secrets.auto.tfvars or environment variables.
rg_name        = "my-app-rg"
location       = "eastus2"
app_name       = "my-linux-webapp"
app_service_tier = "Standard_S1"
virtual_network = "my-vnet"
subnet_name     = "web-subnet"

Modules

Each module follows a consistent pattern with main.tf, variables.tf and outputs.tf.

Example Module - Resource Group

modules/resource_group

  • main.tf
resource "azurerm_resource_group" "this" {
  name     = var.name
  location = var.location
  tags     = var.tags
}
  • variables.tf
  • require name and location
  • Accept a tags map with an empty default value
variable "name" {
  description = "Resource group name"
  type        = string
}

variable "location" {
  description = "Azure region"
  type        = string
}

variable "tags" {
  description = "Resource tags"
  type        = map(string)
  default     = {}
}
  • outputs.tf
output "name" {
  description = "Name of the resource group"
  value       = azurerm_resource_group.this.name
}