Your First Terraform Configuration
Terraform becomes much easier to understand once you stop treating its syntax as a collection of special commands. A Terraform configuration is a description of infrastructure written in a configuration language. You create blocks, give them arguments, and use expressions to connect values together.
In this chapter, you will build a tiny Terraform configuration and use it to see the basic structure of the language.
Terraform Configuration Is Written in HCL
Terraform configurations are normally written using HashiCorp Configuration Language (HCL).
A minimal example looks like this:
resource "example_resource" "example" {
name = "hello"
}There are several things happening here.
resource "example_resource" "example"
β β β
β β βββ local name
β βββ resource type
βββ block typeInside the block is an argument:
name = "hello"The left side is the argument name and the right side is its value.
This gives you the basic shape:
block
βββ labels
βββ argumentsTerraform configurations can also contain expressions. An expression is something Terraform evaluates to produce a value.
For example:
name = var.project_nameHere var.project_name is an expression rather than a literal string.
Your First Working Configuration
For a self-contained first exercise, use Terraform’s random provider. It lets you create a deterministic Terraform-managed resource without needing a cloud account.
Create a directory:
mkdir terraform-first-config
cd terraform-first-configCreate main.tf with this exact content:
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "random" {}
resource "random_pet" "name" {
length = 2
}
output "generated_name" {
value = random_pet.name.id
description = "The generated name."
}This small configuration contains several important pieces.
The terraform block declares the provider dependency.
The provider block configures the provider. This particular provider does not need any configuration, so the block is empty.
The resource block asks Terraform to manage a random_pet resource.
The output block exposes the resulting generated name after Terraform creates it.
Now initialize the directory:
terraform initTerraform downloads the required provider and creates the dependency lock file.
Validate the configuration:
terraform validateYou should see a successful validation message.
Format the configuration:
terraform fmtThen create a plan:
terraform planTerraform should show that it intends to create one resource.
Apply it:
terraform applyTerraform will show the proposed changes and ask for confirmation. Enter:
yesAfter the apply completes, Terraform should display the generated output.
You can also ask Terraform to display the output later:
terraform outputWhen you are finished with the example, clean it up:
terraform destroyConfirm the destruction when Terraform asks.
Understanding the Resource Address
The resource declaration is:
resource "random_pet" "name" {
length = 2
}Terraform identifies this resource using two labels:
random_pet.nameThe first part is the resource type:
random_petThe second part is the local name:
nameThe local name does not need to be the same as the resource’s actual name in an external system. It is Terraform’s name for referring to this resource inside the configuration.
That is why the output can say:
value = random_pet.name.idThe expression means, roughly:
resource type
β
βΌ
random_pet
β
βββ local name: name
β
βββ attribute: idThis resource-reference pattern becomes one of the most important pieces of Terraform syntax you will learn.
Blocks and Arguments
Terraform uses different block types for different purposes.
For example:
resource "random_pet" "name" {
length = 2
}The resource is a block.
Its labels are:
"random_pet"
"name"Its argument is:
length = 2Other Terraform blocks have different structures.
For example:
output "generated_name" {
value = random_pet.name.id
}The output block has one label:
"generated_name"and an argument:
value = random_pet.name.idThe syntax is similar, but the meaning depends on the block type.
Comments
You can add comments to Terraform configuration.
A single-line comment can use #:
# Generate a short random name.
resource "random_pet" "name" {
length = 2
}You can also use // for a single-line comment, and HCL supports block comments:
/*
This resource is only used
for demonstration.
*/
resource "random_pet" "name" {
length = 2
}Use comments to explain decisions or non-obvious behavior. Do not comment every obvious line.
Multiple Terraform Files
As a configuration grows, keeping everything in one file becomes inconvenient.
You can split the same configuration across several .tf files.
For example:
terraform-first-config/
βββ main.tf
βββ outputs.tf
βββ terraform.tfYou could move the terraform block into terraform.tf and the output into outputs.tf.
Terraform still evaluates the files together.
For example, this terraform.tf:
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "random" {}and this main.tf:
resource "random_pet" "name" {
length = 2
}and this outputs.tf:
output "generated_name" {
value = random_pet.name.id
description = "The generated name."
}represent one Terraform configuration.
Terraform does not execute terraform.tf first and then main.tf.
Note
Splitting configuration into files is primarily an organizational decision. Terraform evaluates all top-level .tf files in the directory as one module.
A Practical Configuration Structure
Terraform does not require a particular filename for every block.
You can put the entire first example into main.tf, and it works.
As projects grow, however, a common organization is:
.
βββ main.tf
βββ providers.tf
βββ variables.tf
βββ outputs.tf
βββ locals.tfThe names communicate what a reader is likely to find in each file.
For example:
providers.tfcontains provider configuration.variables.tfcontains input variable declarations.outputs.tfcontains outputs.locals.tfcontains local values.main.tfcan contain the primary resources.
You do not have to follow this exact structure for Terraform to work. The goal is readability.
A larger configuration can also be divided by infrastructure concern:
.
βββ network.tf
βββ compute.tf
βββ storage.tf
βββ outputs.tfThe important rule is that the structure should help someone find a resource quickly.
Configuration Is Not a Script
One of the easiest mistakes to make when first learning Terraform is to imagine this:
main.tf
β
βββ line 1
βββ line 2
βββ line 3
βββ line 4
β
Terraform executes each line in orderThat is not the right mental model.
Terraform reads the configuration as a collection of declarations and expressions. It determines relationships between values and resources and later uses that information when planning changes.
This is why the following does not mean “create A, then create B” merely because A appears first in the file:
resource "..." "a" {
...
}
resource "..." "b" {
...
}Terraform’s behavior is based on the configuration’s meaning and dependencies, not simply on file order.
You will explore this idea in much greater depth later in the course.
The Basic Shape to Remember
At this point, you do not need to memorize every Terraform block.
Keep this mental model:
Terraform configuration
β
βββ blocks
β βββ resource
β βββ provider
β βββ variable
β βββ output
β βββ others
β
βββ arguments
β βββ name = value
β
βββ expressions
βββ values Terraform evaluatesThat structure is the foundation for everything that follows.
You have now created a real Terraform configuration, initialized its provider, validated it, planned it, applied it, inspected its output, and destroyed it. The next chapter will slow down that workflow and examine what each Terraform command actually does.