Terraform Workbook - Your Guide to Infra as Code (IaC)

A companion that helps you create and run Terraform projects with ease. Provides simple explanations and sample contents for common Terraform files, useful related documentation links and essential management commands.

Terraform Workbook - Your Guide to Infra as Code (IaC)

Terraform Project files and purposes

  • vars.tf - default variables declarations
  • terraform.tfvars - set or override default variables values
  • terraform.tf - tfstate backends and providers declarations
  • version.tf - terraform version constraints
  • .terraform.lock.hcl - takes a picture of the installed dependencies for your Terraform project (providers versions essentially). Generated for the first time at Terraform initialization. Should also be place inside your SCV system (e.g, Git) to ensure others get the same providers versions.

A Terraform project also contains other files properly named (e.g, network.tf, database.tf, etc) depending on what you do. Inside thoses files you use 'modules' or specific providers documented 'resources' to manage your infrastructure:

Example vars.tf file

Contains default variables declarations.

# Default variables declarations

# String
variable "env" {
  type    = string

  # Optionally set a description
  description = "Environment of the platform"

  # Optionally set a default value
  default = "staging"

  # Redact value from Terraform CLI log output
  sensitive = true

  # Mask the variable from plan outputs 
  # and do not store it in tfstate
  ephemeral = true
}

# Map of string or object
variable "machine" {
  type = map(string)
  description = "Machine characteristics"
  default = {
    "machine_type"   = "e2-highmem-2"
    "image"          = "ubuntu-os-cloud/ubuntu-2404-lts"
    "data_disk_type" = "pd-standard"
  }
}

# "machine" variable is an object of string.
# We could also declare that variable as follows:

variable "machine" {
  type = object({
    machine_type   = string
    image          = string
    data_disk_type = string
  })

  # optionally set default values
  default = {
    machine_type   = "e2-highmem-2"
    image          = "ubuntu-os-cloud/ubuntu-2404-lts"
    disk_type      = "pd-standard"
  }
}

Example terraform.tvars file

Used to set or override default variables values.

# Set or override default variables values

# Override string type variables
env = "prod"

# Override map or object type variables
machine = {
  machine_type   = "e2-highmem-4"
  image          = "ubuntu-os-cloud/ubuntu-2404-lts"
  disk_type      = "pd-ssd"
}

Example terraform.tf file

Contains Terraform's tfstate backends and providers declarations.

Useful documentations

Terraform.tf file for Microsoft Azure

# Tfstate backends and providers declarations (Azure)

terraform {
  backend "azstorageaccount" {
    storage_account_name = "my-azure-storage-account"
    container_name       = "tfstate"
    key                  = "prod.my-project.tfstate"
  }

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

  provider "azurerm" {
    subscription_id = var.azure_subscription_id
    features {}
  }
}

Terraform.tf file for Google Cloud

# Tfstate backends and providers declarations (Google Cloud)

terraform {
  backend "gcloudstorage" {
    bucket = "mybucket"
    prefix = "myproject/prod"
  }

  required_providers {
    google = {
      source = "hashicorp/google"
      version = "~> 7.4.0"
    }
  }

  provider "google" {
    project = var.google_project_id
  }
}

Example version.tf file

Contains constraints for Terraform version.

# Terraform version constraints

terraform {
  required_version = "~> 1.9"
}

Terraform resources file

Where we declare the resources of the infrastructure Terraform is going to create (virtual machines, managed databases, etc).

# File where you use modules or providers resources
# to create your infrastructure components

resource "google_sql_database_instance" "cloudsql-postgresql" {
(...)
  name     = "my-database-${var.env}"
  project  = var.gcp_project_id

  # Get value from the postgredb map(string) or object
  # type variable containing version as one of the keys
  database_version = var.postgredb.version

  # Set deletion_protection param value to true if
  # var.env value is "prod" otherwise set to false
  deletion_protection = var.env == "prod" ? true : false
(...)
}

Running Terraform

Installation

To install Terraform, have a look at Install Terraform.

You can also use the tfswitch utility to ease the process of installing and switching between different Terraform versions:

# Installing the latest tfswitch version on Linux
# This will deploy the tfswitch binary at /usr/local/bin/tfswitch

# Be root in order to write to /usr/local/bin/
sudo su 

# Deploy tfswitch binary at /usr/local/bin/
curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/master/install.sh | bash

# Go back to standard user
exit

# Run tfswitch and choose the Terraform version you want
tfswitch

# Configure your shell to find the newly
# installed terraform binaries for execution
export PATH="$PATH:${HOME}/bin"
bash # or invok your other shell (sh, zsh, etc)

# Verify
terraform version

Terraform init

# Initialize the working directory

$ terraform init

Initializing the backend...
Initializing modules...
Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v4.46.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see 
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Launch tasks required before being able to create or update the infrastructure:

  • Initialize the backend: ensures the tfstate file is accessible. The tfstate file may be stored locally or remotely depending on your backend configuration.
  • Initialize modules: ensures the Terraform modules you have declared are accessible and available locally
  • Initialize provider plugins: ensures the binaries of the required providers plugins you have declared are accessible and available locally

The downloaded requirements (modules, providers, etc) will be available inside the '.terraform' directory. Ensure your source code version control system ignores it.

Terraform init reconfigure

# Reconfigure the backend
terraform init -reconfigure

Run this when you make changes to the backend configuration (new path for the tfstate or remote storage change).

Terraform init upgrade

# Upgrade providers versions and update the lock file
terraform init -upgrade

You can optionally upgrade the version of the providers plugins based on the version contraints you have declared by using the '-upgrade' flag. The '.terraform.lock.hcl' version lock file will be updated accordingly.

Terraform plan and apply

# Show the changes Terraform will make
terraform plan

# Apply the changes. Will again show you what
# will be performed and ask for a confirmation (yes or no)
terraform apply

You can optionally view or apply changes only for specific resources by using the '-target' flag. Here is an example:

# Target specific resources for plan or apply
terraform plan -target=google_compute_instance.myinstance
terraform apply -target=google_compute_instance.myinstance

Terraform state manipulation

# List resources inside Terraform state
terraform state list

# Show specific resources configurations from Terraform state
terraform state show <resource_id>

# Remove specific resources from Terraform state
terraform state rm <resource_id>

Terraform force-unlock

Sometimes, it may be useful to voluntarily unlock the Terraform state file, for instance after a previous run that didn't properly exit or for any other legitimate reason.

If Terraform state file is locked and you run 'terraform plan' or 'terraform apply', Terraform will tell you that the state file is locked and give you the lock ID.

To unlock the state file, simply run this:

terraform force-unlock -force <lock_id>

Terraform taint

If you want to make Terraform consider that a specific resource should be destroyed and recreated, you can use the taint command as follows:

# Taint a resource : tell Terraform that a 
# resource should be destroyed and recreated
terraform taint <resource_id>

# Untaint the resource
terraform untaint <resource_id>

Want to report a mistake, ask questions or suggest improvement ? Feel free to email me at gmkziz@hackerstack.org.

If you like my articles, consider registering to my newsletter in order to receive the latest posts as soon as they are available.

Take care, keep learning and see you in the next post 🚀