Terraform Tutorial — Blocks, Arguments, and Expressions
Every Terraform configuration is built from blocks. Understanding block anatomy is the single most important thing you need to learn in HCL.
What is a Block?
A block is a container for configuration. It has a type, zero or more labels, and a body enclosed in curly braces.
block_type "label_1" "label_2" {
argument1 = "value1"
argument2 = 42
}
Think of blocks as building blocks (literally). Each block defines one piece of your infrastructure puzzle.
Block Types in Terraform
Terraform has eight core block types:
| Block Type | Purpose | Labels |
|---|---|---|
resource |
Create infrastructure | Type + name |
data |
Read existing infrastructure | Type + name |
variable |
Declare an input variable | Name |
output |
Declare an output value | Name |
provider |
Configure a cloud provider | Provider name |
module |
Call a reusable module | Name |
terraform |
Global settings (versions, backend) | None |
locals |
Define local values | None |
Resource Block
The most common block. It tells Terraform to create a piece of infrastructure:
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-unique-bucket-2024"
tags = {
Environment = "dev"
Team = "backend"
}
}
- Block type:
resource - First label:
"aws_s3_bucket"(resource type — provider + resource) - Second label:
"my_bucket"(local name — how you reference it elsewhere) - Body: Arguments that configure the resource
Variable Block
Declares an input that can be set when running Terraform:
variable "region" {
type = string
default = "us-east-1"
description = "AWS region for all resources"
}
Output Block
Declares a value to display after terraform apply:
output "bucket_arn" {
value = aws_s3_bucket.my_bucket.arn
description = "The ARN of the S3 bucket"
}
The terraform Block
Global settings — version constraints and backend configuration:
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Arguments
Arguments are key-value pairs inside a block body. The key is always on the left, the = sign separates them, and the value is on the right.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0" # string
instance_type = "t3.micro" # string
count = 3 # number
monitoring = true # boolean
}
Argument Types
| Type | Example |
|---|---|
| String | "us-east-1" |
| Number | 8080, 3.14 |
| Boolean | true, false |
| List | ["us-east-1a", "us-east-1b"] |
| Map | { Name = "web", Env = "prod" } |
Expressions
Expressions compute values. They can be simple literals or complex combinations of references, operators, and functions.
Literal Values
name = "my-bucket" # string literal
count = 3 # number literal
enable = true # boolean literal
References
Reference other resources, variables, and data sources:
# Reference a variable
region = var.region
# Reference a resource attribute
vpc_id = aws_vpc.main.id
# Reference a data source attribute
ami = data.aws_ami.ubuntu.id
# Reference a local value
name = local.project_name
# Reference a module output
subnet_id = module.vpc.public_subnet_ids[0]
Operators
# Arithmetic
total = var.base_count * 2
# Comparison
is_prod = var.environment == "production"
# Logical
enable_monitoring = var.environment == "production" && var.monitoring_enabled
Comments
HCL supports three comment styles:
# This is a single-line comment (most common)
// This is also a single-line comment (C-style)
/*
This is a multi-line comment.
Use it for longer explanations.
*/
Most Terraform code uses # for comments. Use /* */ when you need to comment out a large block of code temporarily.
Nested Blocks
Some blocks contain other blocks inside them:
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow HTTP traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
The ingress and egress blocks are nested blocks inside the resource block. They don’t use = — they’re block definitions, not arguments.
What’s Next?
You understand blocks and arguments — the skeleton of every .tf file. Next, let’s learn about string interpolation and the type system that makes HCL powerful.