Terraform HCL Syntax
Introduction
HashiCorp Configuration Language (HCL) is the language used to write Terraform configuration files. It's a structured language specifically designed for defining infrastructure resources in a human-readable format. If you're getting started with Terraform, understanding HCL syntax is your foundation for creating, modifying, and managing infrastructure as code.
In this guide, we'll explore the basic building blocks of HCL and how they work together to define your infrastructure.
HCL Basic Structure
HCL syntax primarily consists of blocks and attributes. Let's break down these fundamental components:
Blocks
Blocks are containers that group content together. They have a type, optional labels, and a body containing arguments.
block_type "optional_label" "another_optional_label" {
// Block body - contains arguments
key = value
}
Real-world examples of blocks include:
# Resource block for creating an AWS EC2 instance
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
# Provider block for configuring AWS provider
provider "aws" {
region = "us-west-2"
}
Attributes
Attributes are simple key-value pairs that configure the properties of a block:
key = value
Values can be:
- Strings:
"hello"
- Numbers:
5
,3.14
- Booleans:
true
,false
- Lists:
["item1", "item2"]
- Maps:
{key1 = "value1", key2 = "value2"}
Comments
HCL supports three forms of comments:
# This is a single line comment
// This is also a single line comment
/*
This is a
multi-line comment
*/
Working with Variables and Expressions
Variables
In Terraform, variables make your configurations more flexible and reusable. Variables are defined in a variable
block:
variable "instance_type" {
description = "The type of EC2 instance to launch"
type = string
default = "t2.micro"
}
You can then reference these variables using the var
keyword:
resource "aws_instance" "example" {
instance_type = var.instance_type
}
Expressions
HCL supports various expressions for manipulating values:
String Interpolation
String interpolation allows you to include the result of an expression within a string:
resource "aws_instance" "web" {
tags = {
Name = "Web server for ${var.environment}"
}
}
Conditional Expressions
Terraform supports ternary conditional expressions:
resource "aws_instance" "example" {
instance_type = var.environment == "production" ? "m5.large" : "t2.micro"
}
For Expressions
For expressions create complex data structures by transforming one list or map into another:
locals {
subnet_ids = [
"subnet-abcdef",
"subnet-123456",
"subnet-789012"
]
# Create tags for each subnet
subnet_tags = {
for id in local.subnet_ids :
id => "Subnet ${id}"
}
}
Common HCL Blocks
Provider Block
The provider block configures a specific provider, which is a plugin that Terraform uses to create and manage resources:
provider "aws" {
region = "us-east-1"
profile = "default"
}
Resource Block
Resource blocks define infrastructure objects like virtual networks, compute instances, or DNS records:
resource "aws_s3_bucket" "data" {
bucket = "my-terraform-bucket"
acl = "private"
tags = {
Name = "My bucket"
Environment = "Dev"
}
}
Data Block
Data blocks fetch information from provider APIs or other Terraform states:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical's AWS account ID
}
Output Block
Output blocks define values that will be highlighted to the user when Terraform applies, and can be queried using the terraform output command:
output "instance_ip_addr" {
value = aws_instance.web.private_ip
description = "The private IP address of the web server"
}
Special Syntax Features
Heredoc Strings
For multi-line strings, HCL provides heredoc syntax:
user_data = <<-EOF
#!/bin/bash
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &
EOF
Dynamic Blocks
Dynamic blocks let you dynamically construct repeatable nested blocks:
resource "aws_security_group" "example" {
name = "example"
dynamic "ingress" {
for_each = var.service_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
Practical Example: Creating a Complete AWS Infrastructure
Let's build a practical example that brings together many of the HCL concepts we've covered. This example sets up a basic web server infrastructure on AWS:
# Define the AWS provider
provider "aws" {
region = var.aws_region
}
# Define variables
variable "aws_region" {
description = "AWS region to deploy resources"
default = "us-west-2"
}
variable "vpc_cidr" {
description = "CIDR block for the VPC"
default = "10.0.0.0/16"
}
variable "subnet_cidr" {
description = "CIDR block for the subnet"
default = "10.0.1.0/24"
}
variable "instance_type" {
description = "EC2 instance type"
default = "t2.micro"
}
# Create a VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform-example-vpc"
}
}
# Create a subnet within the VPC
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
tags = {
Name = "terraform-example-subnet"
}
}
# Create a security group
resource "aws_security_group" "web" {
name = "web-server-sg"
description = "Allow HTTP/HTTPS and SSH traffic"
vpc_id = aws_vpc.main.id
# HTTP access from anywhere
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# SSH access from anywhere (not recommended for production)
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Outbound internet access
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Get the latest Amazon Linux AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# Create an EC2 instance
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.main.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.html
EOF
tags = {
Name = "terraform-example-webserver"
}
}
# Output the public IP of the web server
output "web_server_ip" {
value = aws_instance.web.public_ip
}
HCL Syntax Visualization
Here's a diagram that illustrates the relationship between different HCL components:
Best Practices for HCL
-
Use consistent formatting: Terraform includes a
terraform fmt
command that automatically formats your configuration files according to the recommended style. -
Modularize your code: Use modules to encapsulate and reuse configuration.
-
Use variables effectively: Define variables with clear descriptions, types, and sensible defaults.
-
Comment your code: Include comments explaining complex logic or decisions.
-
Follow naming conventions: Use snake_case for resource names and descriptive identifiers.
-
Version your configurations: Use a version control system like Git to track changes.
-
Validate before applying: Always run
terraform validate
andterraform plan
before applying changes.
Summary
In this guide, we've covered the essentials of Terraform's HCL syntax:
- Basic structures like blocks and attributes
- Working with variables and expressions
- Common block types and their purposes
- Special syntax features like heredocs and dynamic blocks
- A practical example bringing everything together
HCL's declarative nature makes it powerful for defining infrastructure as code. By understanding these syntax elements, you're now ready to start creating your own Terraform configurations.
Additional Resources
- Practice by creating a simple infrastructure for a different cloud provider
- Try implementing a multi-environment setup using variables and conditionals
- Experiment with the
terraform console
command to test expressions - Consider these exercises:
- Create a configuration that provisions multiple similar resources using
count
orfor_each
- Convert an existing manual infrastructure into Terraform HCL code
- Create a module that can be reused across different projects
- Create a configuration that provisions multiple similar resources using
Next Steps
Now that you understand HCL syntax, you're ready to dive deeper into Terraform's state management, modules, and workflow commands.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)