What Is HCL?
HCL stands for HashiCorp Configuration Language. It is the language you write Terraform configurations in. Every .tf file in a Terraform project is written in HCL. The goal of HCL is to sit in the sweet spot between JSON (machine-friendly but hard to read) and a full programming language (powerful but complex for configuration).
HCL is also used by other HashiCorp tools — Packer, Vault, Consul, and Nomad all use HCL for their configuration files.
HCL Syntax Basics
# This is a comment # A block has a type, optional labels, and a bodyresource "aws_s3_bucket" "order_images" { # type=resource, labels="aws_s3_bucket" "order_images" bucket = "swiggy-order-images-prod" # argument: name = value tags = { # map value Environment = "production" Team = "platform" }} # String interpolationlocals { bucket_name = "swiggy-${var.environment}-images" # ${} for interpolation} # Multiline string (heredoc)resource "aws_instance" "web" { user_data = <<-EOF #!/bin/bash echo "Hello from ${var.environment}" > /tmp/init.txt apt-get update -y EOF}HCL vs JSON
Terraform accepts both HCL (.tf) and JSON (.tf.json). HCL is preferred for human-written files:
# HCL — readable, supports commentsresource "aws_s3_bucket" "example" { bucket = "my-bucket" tags = { Environment = "prod" }}// JSON equivalent — verbose, no comments{ "resource": { "aws_s3_bucket": { "example": { "bucket": "my-bucket", "tags": { "Environment": "prod" } } } }}Types in HCL
variable "examples" { # Primitive types # string = "ap-south-1" # number = 3 # bool = true # Collection types # list(string) = ["a", "b", "c"] # map(string) = { key = "value" } # set(string) = toset(["a", "b"]) # Structural types # object({ name = string, count = number }) # tuple([string, number, bool])}Expressions and Functions
locals { # Conditional expression (ternary) instance_type = var.environment == "prod" ? "t3.large" : "t3.small" # Built-in functions upper_env = upper(var.environment) # "PROD" joined = join("-", ["zerodha", "prod"]) # "zerodha-prod" has_items = length(var.subnet_ids) > 0 # true/false # For expression — transform a list upper_tags = [for tag in var.tags : upper(tag)] # For expression — transform a map tag_map = { for k, v in var.raw_tags : k => lower(v) }}Dynamic Blocks
# Instead of repeating ingress blocks manually:resource "aws_security_group" "app" { name = "app-sg" # Generate multiple ingress rules from a variable dynamic "ingress" { for_each = var.allowed_ports # e.g., [80, 443, 8080] content { from_port = ingress.value to_port = ingress.value protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } }}HCL Formatting
# Format all .tf files to standard styleterraform fmt # Check formatting without changing files (use in CI)terraform fmt -check # Format recursively through subdirectoriesterraform fmt -recursivePLACEMENT PRO TIP**Tip:** Run `terraform fmt` before every commit. Add `terraform fmt -check` to your CI pipeline to reject unformatted code. This keeps the codebase consistent across engineers.
REMEMBER THIS**Remember:** HCL is not a general-purpose programming language. It has no loops in the traditional sense (use `for_each` and `for` expressions), no if/else blocks (use ternary), and no functions you can define. For complex logic, you need to think in HCL's declarative model.