Skip to main content
Navigation
HomeTechnical ReferenceJournalGitHubGitHub
Sidebar — toggle document categories via the logo
Categories

Terraform Configuration Syntax

Overview

HCL (HashiCorp Configuration Language) is Terraform's domain-specific language. It balances human readability with machine-friendliness. This reference covers every major syntax construct — from basic resource definitions to auto-scaling clusters with load balancers.

Resources

A resource is the fundamental building block in Terraform. Each resource block declares one infrastructure object.

resource "aws_instance" "web" {
ami = "ami-0fb653ca2d3203ac1"
instance_type = "t2.micro"
tags = {
Name = "web-server"
}
}
PartMeaning
aws_instanceResource type — the kind of infrastructure to create.
"web"Local name — how you refer to this resource in other parts of the config.
{ ... }Arguments — the configuration for this specific resource.

Referencing resource attributes

Use <RESOURCE_TYPE>.<NAME>.<ATTRIBUTE>:

resource "aws_eip" "web" {
instance = aws_instance.web.id
}

output "public_ip" {
value = aws_eip.web.public_ip
}

Common meta-arguments

Meta-argumentPurposeExample
depends_onExplicit dependencydepends_on = [aws_s3_bucket.logs]
countCreate N copiescount = 3
for_eachCreate one per map/set entryfor_each = toset(["a", "b"])
providerUse non-default providerprovider = aws.west
lifecycleControl resource lifecyclelifecycle { create_before_destroy = true }
tagsResource tags (most providers)tags = { Name = "web" }

Variables

Variables make configurations reusable and environment-independent.

Variable declarations

# variables.tf

variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}

variable "server_port" {
description = "Port the web server listens on"
type = number
default = 8080
}

variable "enable_monitoring" {
description = "Enable detailed CloudWatch monitoring"
type = bool
default = false
}

variable "instance_tags" {
description = "Tags to apply to instances"
type = map(string)
default = {
Project = "myapp"
Environment = "dev"
}
}

variable "subnet_ids" {
description = "List of subnet IDs"
type = list(string)
default = []
}

variable "app_config" {
description = "Application configuration object"
type = object({
name = string
version = string
ports = list(number)
})
}

Variable types

TypeExample value
string"t2.micro"
number8080
booltrue
list(string)["a", "b", "c"]
map(string){ Name = "web", Env = "prod" }
set(string)toset(["a", "b"])
object({...}){ name = "app", version = "1.0", ports = [80, 443] }
anyAccept any type (use sparingly)

Setting variable values

# Command line
terraform apply -var="instance_type=t3.medium"

# Variable definition files (.tfvars)
# terraform.tfvars (auto-loaded) or custom file
terraform apply -var-file="prod.tfvars"
# terraform.tfvars
instance_type = "t3.medium"
server_port = 8080

instance_tags = {
Project = "myapp"
Environment = "production"
}

Environment variables

Prefix the variable name with TF_VAR_:

export TF_VAR_instance_type="t3.medium"
export TF_VAR_server_port="8080"

Outputs

Outputs expose values after applying — for display to the user or for consumption by other configurations.

# outputs.tf

output "instance_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.web.public_ip
}

output "alb_dns_name" {
description = "DNS name of the load balancer"
value = aws_lb.main.dns_name
}

output "database_endpoint" {
description = "RDS endpoint address"
value = aws_db_instance.main.address
sensitive = false
}
# Query outputs
terraform output
terraform output alb_dns_name
terraform output -json

Data sources

Data sources query existing infrastructure without managing it — read-only lookups.

# Fetch the default VPC
data "aws_vpc" "default" {
default = true
}

# Fetch all subnets in the default VPC
data "aws_subnets" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}

# Look up a specific AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}

String interpolation

Reference variables and resource attributes inside strings with ${}:

resource "aws_launch_configuration" "app" {
name_prefix = "app-${var.environment}-"
user_data = templatefile("${path.module}/user-data.sh", {
server_port = var.server_port
db_address = var.db_address
})
}

Locals

locals define reusable values that don't change per environment:

locals {
http_port = 80
https_port = 443
any_port = 0
tcp_protocol = "tcp"
all_ips = ["0.0.0.0/0"]

common_tags = {
ManagedBy = "terraform"
Environment = var.environment
}
}

resource "aws_security_group" "web" {
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}

tags = merge(local.common_tags, { Name = "web-sg" })
}

Security groups

resource "aws_security_group" "instance" {
name = "web-server-sg"
vpc_id = data.aws_vpc.default.id

ingress {
from_port = var.server_port
to_port = var.server_port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow inbound HTTP"
}

ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
description = "Allow SSH from internal network"
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
}
}

Security group rules (separate resource)

For cleaner management, use standalone aws_security_group_rule resources:

resource "aws_security_group" "instance" {
name = "web-sg"
}

resource "aws_security_group_rule" "allow_http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.instance.id
}

Auto-scaling group with load balancer

This is the canonical pattern for a stateless web service:

data "aws_vpc" "default" {
default = true
}

data "aws_subnets" "default" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
}

# Security group for instances
resource "aws_security_group" "instance" {
name = "web-instance-sg"
ingress {
from_port = var.server_port
to_port = var.server_port
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}

# Launch configuration (template for EC2 instances)
resource "aws_launch_configuration" "web" {
image_id = "ami-0fb653ca2d3203ac1"
instance_type = var.instance_type
security_groups = [aws_security_group.instance.id]
user_data = templatefile("${path.module}/user-data.sh", {
server_port = var.server_port
})

lifecycle {
create_before_destroy = true
}
}

# Auto-scaling group
resource "aws_autoscaling_group" "web" {
launch_configuration = aws_launch_configuration.web.name
vpc_zone_identifier = data.aws_subnets.default.ids

target_group_arns = [aws_lb_target_group.web.arn]
health_check_type = "ELB"

min_size = var.min_size
max_size = var.max_size

tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}

# Application Load Balancer
resource "aws_lb" "web" {
name = var.cluster_name
load_balancer_type = "application"
subnets = data.aws_subnets.default.ids
security_groups = [aws_security_group.alb.id]
}

resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.web.arn
port = 80
protocol = "HTTP"

default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: Not Found"
status_code = "404"
}
}
}

resource "aws_lb_target_group" "web" {
name = var.cluster_name
port = var.server_port
protocol = "HTTP"
vpc_id = data.aws_vpc.default.id

health_check {
path = "/"
protocol = "HTTP"
matcher = "200"
interval = 15
timeout = 3
healthy_threshold = 2
unhealthy_threshold = 2
}
}

resource "aws_lb_listener_rule" "web" {
listener_arn = aws_lb_listener.http.arn
priority = 100

action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}

condition {
path_pattern {
values = ["*"]
}
}
}

# Security group for ALB
resource "aws_security_group" "alb" {
name = "web-alb-sg"
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"]
}
}

See also