Skip to main content

The Blueprint Language — HCL

Before the builder hands a blueprint to the contractor, everyone must agree on the notation. Terraform uses a small, readable language called HCL — HashiCorp Configuration Language. It's designed to look like a list of instructions a human could read, while still being exact enough for a machine to follow.

Every .tf file is written in HCL. Learn the notation once and you can read any Terraform configuration on Earth.

Blocks and Arguments​

Everything in HCL is a block — a labeled section wrapped in braces. Inside a block are arguments: key = value pairs.

A block with arguments
resource "aws_s3_bucket" "library_archive" {
bucket = "campus-library-archive"
tags = {
Name = "Library Archive"
}
}
  • resource is the type of block.
  • "aws_s3_bucket" and "library_archive" are the block labels — together they give the block a unique address: aws_s3_bucket.library_archive.
  • bucket and tags are arguments.
  • Comments use # for a line and /* ... */ for a block.
Remember

Read a block aloud: "declare an aws_s3_bucket called library_archive." The pattern type → name → settings is used everywhere in Terraform.

Data Types​

Every value has a type. The ones you'll see daily:

TypeExampleMeaning
string"eu-west-1"Text, always in quotes
number42A whole or decimal number
booltruetrue or false
list(string)["admin", "reader"]An ordered list, accessed by index
map(string){ dev = "blue" }Key → value pairs
object{ size = 4, name = "villa" }A named group of values
tuple["a", 1, true]A fixed list where positions have different types
nullnull"No value" — used to clear an argument

Expressions and Interpolation​

Values can be computed. The most common pattern is interpolation — inserting a value into a string:

bucket = "library-${var.env}-archive"

Here ${var.env} is replaced with the value of the variable env. So if env = "prod", the bucket is library-prod-archive.

You can also combine with operators and ternary conditionals:

instance_type = var.workload == "production" ? "t3.large" : "t2.micro"
Remember

Read condition ? yes : no as "if this is true, use the first value; otherwise use the second." It's the same conditional the builder uses to pick a bigger villa for hilly land.

Common Functions​

HCL has built-in functions that transform values:

length(var.cities) # number of items in a list or map
join(",", var.regions) # "eu-west-1,eu-west-2"
split(",", "a,b,c") # ["a", "b", "c"]
lookup(var.owners, "dev", "nobody") # value for key, or default
concat(list1, list2) # merge two lists
tostring(42) # convert 42 to "42"
file("README.txt") # read a file's contents

Function calls always look like name(arg1, arg2). You can find the full list in the Terraform docs, but these six cover most of daily life.

Locals — Giving Names to Values​

A local is a named value you compute once and reuse. It's the builder's shorthand — "the standard foundation" instead of spelling out the whole recipe every time.

locals {
common_tags = {
Project = "Campus Library"
ManagedBy = "Terraform"
Env = var.env
}
}

resource "aws_s3_bucket" "archive" {
bucket = "archive-${var.env}"
tags = locals.common_tags
}

Locals are never set from the command line; they're always computed inside the configuration. Use them to avoid repeating yourself.

Repetition — count and for_each​

Building ten identical villas by copying the block ten times is exactly the manual work we're avoiding. Two meta-arguments repeat resources:

count — repeat a whole number of times​

resource "aws_s3_bucket" "city" {
count = 3
bucket = "library-${count.index}"
}

This creates aws_s3_bucket.city[0], [1], and [2].

for_each — repeat once per item in a map or set​

resource "aws_s3_bucket" "city" {
for_each = toset(["dev", "staging", "prod"])
bucket = "library-${each.key}"
}

This creates one bucket per city name, addressed as aws_s3_bucket.city["dev"] and friends.

Remember

count = "three rooms." for_each = "one room per name in this list." Choose for_each when the items have meaningful names — it makes the state easier to read and resources easier to reference later.

Reading a Blueprint​

Putting it together, a small configuration reads like plain English:

variable "env" {
type = string
default = "dev"
}

locals {
common_tags = {
Project = "Campus Library"
Env = var.env
}
}

resource "aws_s3_bucket" "archive" {
bucket = "library-${var.env}-archive"
tags = locals.common_tags
}

output "archive_arn" {
value = aws_s3_bucket.archive.arn
}

Read it aloud: "Set a variable env, default dev. Compute some common tags. Create an S3 bucket named library-dev-archive. Print the bucket's ARN." That's Terraform.

Next, learn the commands that turn this blueprint into real infrastructure.