DevOps Rich DevOps content for Infrastructure Engineers

Terraform Basics



3 Fundamental Block Types:

Note: Only constant values are allowed in this block no variable references.

Additional Block Types:


Provider plugins are downloaded by terraform cli and used to interact with Cloud APIs e.g. the azure provider communicates with the Microsoft Azure API. They have their own release cycles and version numbers.

Required Provider Block

This is contained within the terraform block and includes the source and version as shown below:

terraform {
  required_version = ">= 1.0.0"
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.0"

The source is prefixed with by default.

Provider block

This block allows you to change the default behaviour of a provider such as:

Note: You can call a provider alias and thus feature settings in a resource block.

provider "azurerm" {
  features {}

Note: Local names should match between the required_providers and provider block. It is recommended to use the vendor preferred name e.g. azurerm but you could use anything in theory.

Dependency Lock File

It prevents you from updating a provider plugin version. It is best practice to check in the lock file into your code repository.

terraform init -upgrade - this will upgrade the lock file if you decide to change the allowed provider versions.

Note: It only includes provider version tracking not Terraform cli nor modules.

Provider Badges

When you navigate to the terraform registry list of providers you will notice they have 1 of 3 badges:

Resource Block Syntax

It contains:

resource "azurerm_resource_group" "resource_group" {
  location = "uksouth"
  name = "rg-01"  

Internal Block

A block within a resource block e.g. Ip_configuration {} in azurerm_network_interface resource.

Note: Whereas, tag = {} is an argument using a map value.

Resource Behaviour

Note: It first destroys and then recreates. You can change this behaviour using meta-arguments.


Note: It is not recommended to manually edit the state file nor store it locally.


Changes behaviour of resource blocks:

Note: Provisioners & Connections are not meta-arguments, they can invoke extra actions after resource creation or destruction e.g. install app on VM.


depends_on = [


resource "azurerm_public_ip" "pip" {
  count = 2
  name                = "pip-${count.index}"
  resource_group_name =
  location            = azurerm_resource_group.rg.location
  allocation_method   = "Static" 


For_each = {
Dev = "myapp1"

Each.key = dev

Each.value = myapp1
 for_each = azurerm_network_interface.nic


lifecycle {
    create_before_destroy = true
  1. Prevent_destroy - database/ disk that you do not want to be removed.
lifecycle {
    prevent_destroy = true
  1. Ignore_changes - ignore change made within Azure portal e.g. tags.
lifecycle {
    ignore_changes = [ tags, ]


file function

This can be used to read the contents of a file e.g. obtain ssh public key within azurerm_linux_virtual_machine resource block.

admin_ssh_key {
    username = "localadmin"
    public_key = file("${path.module}/ssh-keys/")


This can be used to read the contents of a file Base64-encoded e.g. used by the Custom_data argument within azurerm_linux_virtual_machine resource block to reference cloud init file that can configure a VM.

custom_data = filebase64("${path.module}/scripts/cloud_init.txt")


element(["blue","red","green"], 1)




resource "azurerm_resource_group" "rg" {
  for_each = toset([ "uksouth", "ukwest" ])
  name = "rg-${each.key}"
  location = each.key 

Splat expression

The following example illustrates how to reference an instance of a NIC when both resource block azurerm_windows_virtual_machine and azurerm_network_interface are using the count meta-argument:

network_interface_ids = [
    element(azurerm_network_interface.winvm_nic[*].id, count.index)