What HCL is
HCL stands for HashiCorp Configuration Language, a language HashiCorp built specifically to describe infrastructure. In it you write "I want one S3 bucket with these tags and one EC2 instance next to it," and Terraform turns that into API calls to the cloud.
HCL is not a programming language in the full sense. It is a declarative language: you describe the desired state, not a sequence of steps. That is the main difference from Python or bash.
What a .tf file is made of
An HCL file is a set of blocks. Each block describes one thing: a resource, a variable, a provider, an output. The basic structure of a block:
block_type "first_label" "second_label" {argument_one = "value"
argument_two = 42
nested_block {sub_argument = true
}
}
A concrete example, the description of an S3 bucket:
resource "aws_s3_bucket" "demo" {bucket = "my-unique-name-12345"
tags = {Owner = "student"
Project = "terraform-hello"
}
}
Here:
resourceis the block type (we are creating a resource)."aws_s3_bucket"is the first label, which says what kind of resource this is."demo"is the second label, our internal name for this bucket. Inside the file you can refer to it asaws_s3_bucket.demo.bucketandtagsare the block's arguments.
The block types you see most often
resource: create something in the cloud (a bucket, a VM, a database).data: read something from the cloud (find the ID of an existing subnet). See tf-resource-block and tf-data-source later.variable: an input parameter for the configuration (the bucket name from outside).output: what to return to the outside (the ID of the bucket you created).provider: which provider is used and with which settings (AWS, GCP, Azure).terraform: a meta block with settings for Terraform itself: which provider versions, where to store state.locals: computed internal values.module: pull in a reusable module (not used in the beginner track).
Expressions and interpolation
Values in HCL do not have to be strings. You can reference other resources, call functions, do arithmetic:
resource "aws_s3_bucket" "logs" { bucket = "${aws_s3_bucket.demo.bucket}-logs"# read this as: "take the bucket attribute of resource aws_s3_bucket.demo and append -logs"
}
output "bucket_count" {value = length(aws_s3_bucket.demo.tags)
# length is a built-in function that counts the elements of a list or map
}
The ${...} construct is called interpolation, a substitution. In modern HCL you can often drop it when the expression is the only content of the string: bucket = aws_s3_bucket.demo.id works the same as bucket = "${aws_s3_bucket.demo.id}".
Comments
HCL has three ways to comment something out:
# single line (the most common)
// also single line (a C-style holdover)
/*
multi-line
block comment
*/
Use #: that is the HashiCorp convention.
How HCL differs from JSON
JSON is also a valid format for Terraform, called JSON syntax (.tf.json). But in real work nobody writes it by hand. The reasons:
- In HCL you can write comments. In JSON you cannot.
- In HCL you do not need quotes around keys or trailing commas in lists.
- HCL has readable multi-line strings through
heredoc(<<EOF ... EOF). - In HCL it is easier to reference other resources, without nested quotes.
The JSON format is useful only when the HCL file is generated by another program.
Gotchas
- Block labels are case sensitive.
ResourceorRESOURCEis invalid; onlyresourceworks. - Resource names within one type must be unique. Two
resource "aws_s3_bucket" "demo"blocks is an error, even if the bucket names differ. - A comma after the last element of a list or map is optional but allowed. Unlike JSON.
- Heredoc keeps line breaks. If you need line breaks in the string, use
<<-EOF(with a hyphen): it strips the leading indentation.