This is the start of a series of posts to help introduce and learn the Terraform HCL language. It is written for those with an elementary understanding of programming already. This allows us to cover things effectively instead of being stuck in the weeds.

In this post, we’ll cover the typical Terraform project structure, and provide a gentle introduction to Terraform resources, variables, and outputs. As a part of this, we’ll also go through the starter commands: terraform init, apply, and destroy.

Note, we’ll use a local backend statefile for simplicity. For more information about statefile management, these docs can be helpful: State File Approaches.

Structure Overview

When first learning a tool or language, it’s often useful to start with an overview of folders and files structure. The recommended terraform structure is:

demo/
├── main.tf
├── outputs.tf
└── variables.tf

What the Files Do

The main.tf file is where you define the resources for Terraform to provision. Example:

resource "random_pet" "this" {
  length = var.length
}

The random_pet resource is useful for testing. It creates random but deterministic “pet names”.

The variables.tf file defines variables that can be used to dynamically change what is provisioned. Example:

variable "length" {
  type    = number
  default = 2
}

The outputs.tf file defines a list of outputs that will show up when deploying with terraform apply. Example:

output "pet" {
  value = random_pet.this
}

The code for these examples is here: 1-intro-basics/1-separate

One File for Quick Testing

For quick testing and learning, sometimes people throw everything into one main.tf file.

variable "length" {
  type    = number
  default = 2
}

resource "random_pet" "this" {
  length = var.length
}

output "pet" {
  value = random_pet.this
}

This brings up a good point. Terraform loads and evaluates all .tf files in the folder and “flattens” them down. The main.tf, variables.tf, and outputs.tf are just recommended files. Generally, I recommend sticking to these files, though. It’s what others looking through your code will expect to see.

The code for these examples is here: 1-intro-basics/2-combined

Deploy Terraform Code

To deploy:

$ terraform init
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # random_pet.this will be created
  + resource "random_pet" "this" {
      + id        = (known after apply)
      + length    = 2
      + separator = "-"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + pet = {
      + id        = (known after apply)
      + keepers   = null
      + length    = 2
      + prefix    = null
      + separator = "-"
    }

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

random_pet.this: Creating...
random_pet.this: Creation complete after 0s [id=viable-possum]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

pet = {
  "id" = "viable-possum"
  "length" = 2
  "separator" = "-"
}
$

The terraform init command “initializes” a Terraform working directory. This step evaluates your terraform code and downloads dependencies. It loads remote state, modules, and provider plugins like AWS. This is how terraform knows how to create cloud-specific resources. In a way, it’s like a bundle install, but it’s more complex.

The terraform apply is when the cloud infrastructure is actually deployed and created.

Update

To update the infrastructure, we’ll introduce and use a tfvars file. A tfvars file is a convenient way to set one or more variable values.

Create a dev.tfvars with these contents:

length = 3

Then apply again using the -var-file option.

$ terraform apply -var-file dev.tfvars
random_pet.this: Refreshing state... [id=viable-possum]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # random_pet.this must be replaced
-/+ resource "random_pet" "this" {
      ~ id        = "viable-possum" -> (known after apply)
      ~ length    = 2 -> 3 # forces replacement
        separator = "-"
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Changes to Outputs:
  ~ pet = {
      ~ id        = "viable-possum" -> (known after apply)
        keepers   = null
      ~ length    = 2 -> 3
        prefix    = null
        separator = "-"
    }

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

random_pet.this: Destroying... [id=viable-possum]
random_pet.this: Destruction complete after 0s
random_pet.this: Creating...
random_pet.this: Creation complete after 0s [id=heavily-beloved-egret]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

Outputs:

pet = {
  "id" = "heavily-beloved-egret"
  "length" = 3
  "separator" = "-"
}
$

Using tfvars files allow you to specify different variable values for different deployed modules. This is useful because it allows you to use the same code to create infrastructure with different settings, like for dev and prod environments.

Destroy

Finally, let’s clean up and destroy the resources.

$ terraform destroy
random_pet.this: Refreshing state... [id=heavily-beloved-egret]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # random_pet.this will be destroyed
  - resource "random_pet" "this" {
      - id        = "heavily-beloved-egret" -> null
      - length    = 3 -> null
      - separator = "-" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Changes to Outputs:
  - pet = {
      - id        = "heavily-beloved-egret"
      - keepers   = null
      - length    = 3
      - prefix    = null
      - separator = "-"
    } -> null

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

random_pet.this: Destroying... [id=heavily-beloved-egret]
random_pet.this: Destruction complete after 0s

Destroy complete! Resources: 1 destroyed.
$

The random_pet now has been cleaned up.

Summary

That’s an elementary but important introduction to Terraform main components: Resources, Variables, Outputs. In the next post, Terraform HCL Intro 2: Function Analogy, we’ll use an analogy to compare these components to a “function” and also introduce locals.

The source code for the examples is available at: terraform-hcl-tutorials/1-intro-basics

Want It to be Easier to Work with Terraform?

Check out Terraspace: The Terraform Framework.

The Terraform HCL Language Intro Tutorials