Skip to main content

Writing Configuration Files (HCL)

1. Understanding HCL Syntax

Terraform Configuration Language (HCL) is a domain-specific language used to define infrastructure as code. HCL supports both a native syntax and JSON syntax, though the native syntax is preferred for its readability.

Element Description Example
Blocks Structural elements that define resources, data sources, providers, etc.
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}
Attributes Simple key-value pairs within blocks. ami = "ami-12345678"
Lists Ordered collections of values. tags = ["web", "production"]
Maps Key-value collections with string keys. labels = { env = "prod", team = "backend" }
Expressions Values computed from other values using operators or functions. count = length(var.instances)

Example: Basic HCL Block

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-unique-bucket-name"
  acl    = "private"

  tags = {
    Name        = "My S3 Bucket"
    Environment = "Production"
  }
}
Note: HCL supports interpolation using ${} syntax, which allows referencing values from other parts of the configuration.
Warning: HCL is case-sensitive. Attribute names and resource types must match exactly.

Key Concepts

  • Blocks define the structure of your configuration.
  • Attributes are key-value pairs inside blocks.
  • Lists and Maps are used to represent collections of data.
  • Expressions allow dynamic computation within the configuration.

2. Creating Terraform Files

Example: Basic Terraform Configuration File

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}
Element Description Required
resource Defines a resource to be created Yes
data Defines a data source to be read No
variable Defines an input variable for the configuration No
output Defines an output value to be displayed after apply No
module Includes configuration from another directory or module No
Note: Terraform configuration files use the .tf extension.
Warning: Avoid hardcoding sensitive values like passwords or API keys in configuration files.

3. Organizing Configuration Files

Directory Structure Best Practices

  1. Create a root directory for your Terraform project
  2. Organize configuration files by environment (e.g., dev/, stg/, prod/)
  3. Use modules to encapsulate reusable components
  4. Place backend configuration in .terraformrc or backend.tf
  5. Store sensitive data in terraform.tfvars or external secret stores
Directory Description Usage
main.tf Main configuration file Define resources and providers
variables.tf Variable definitions Externalize configuration values
outputs.tf Output definitions Expose resource values to users
modules/ Module directory Reusable, encapsulated configurations
backend.tf Backend configuration Define state storage and locking
terraform.tfvars Variable values Environment-specific variable overrides

Example: Module Organization

module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  region = "us-west-2"
}
Note: Use terraform init after organizing files to ensure all modules are properly initialized.

Key Principles

  • Keep configurations DRY (Don't Repeat Yourself)
  • Use module blocks to compose complex infrastructure
  • Separate concerns by environment and resource type
  • Use terraform.tfvars for environment-specific values

4. Using Comments (# single-line, /* */ multi-line)

Comment Type Syntax Usage
Single-line comment # This is a single-line comment Used to add brief notes or disable lines of configuration
Multi-line comment /* This is a multi-line comment */ Used for longer explanations or temporarily disabling blocks of code

Example: Using Comments in HCL

resource "aws_instance" "web" {
  # Create an EC2 instance
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  /* 
   * This block is commented out
   * and will not be processed
   *
   * root_block_device {
   *   volume_size = 20
   * }
   */
}

5. Defining Resource Blocks

Property Description Example
type Resource type (e.g., aws_instance, google_compute_instance) aws_instance
name Unique name for the resource within the configuration web_server
depends_on List of resources this resource depends on ["aws_security_group.web"]
count Number of instances to create (optional) 3
provider Provider to use for this resource (optional) aws.west

Example: Basic Resource Block

resource "aws_instance" "web_server" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  tags = {
    Name = "WebServer"
  }
}

Example: Resource with Count

resource "aws_instance" "web_server" {
  count         = 3
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  tags = {
    Name = "WebServer-${count.index}"
  }
}

Example: Resource with Dependencies

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

resource "aws_instance" "web_server" {
  depends_on = [aws_security_group.web]
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

6. Setting Block Labels

Block Type Label Syntax Description
resource resource "aws_instance" "web" { } Defines a resource with type aws_instance and label web
data data "aws_ami" "ubuntu" { } Defines a data source with type aws_ami and label ubuntu
variable variable "instance_type" { } Defines a variable with label instance_type
output output "public_ip" { } Defines an output with label public_ip
module module "vpc" { } Defines a module with label vpc

Example: Resource with Labels

resource "aws_instance" "web_server" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

Example: Data Source with Labels

data "aws_ami" "latest_ubuntu" {
  most_recent = true
  owners      = ["099720109473"]
  filters {
    name   = "ubuntu/images/*"
    values = ["18.04"]
  }
}
Note: Labels must be unique within the scope of a block type and must follow HCL identifier rules (alphanumeric and underscore only, cannot start with a number).

7. Using Arguments and Attributes (key = value)

Concept Description Example
Arguments Values provided to a block or function, typically used for configuration count = 2
Attributes Named values within a block that define properties of the resource or module name = "web-server"

Example: Resource with Arguments and Attributes

resource "aws_instance" "web" {
  count = 2
  ami   = "ami-12345678"
  instance_type = "t2.micro"
  tags = {
    Name = "web-server"
  }
}
Note: Arguments and attributes are used to configure resources, data sources, and modules in HCL. Arguments can be expressions or literals, while attributes are key-value pairs that define properties.

8. Understanding Meta-Arguments

Argument Description Values Notes
count Number of instances to create number Creates multiple instances of a resource. Use count.index to reference each instance.
for_each Creates instances for each key-value pair in a map or set map(string) or set(string) Useful for creating multiple resources with different configurations. Iterates over keys and values.
depends_on Explicitly declares dependencies between resources list(resource) Ensures that Terraform applies resources in the correct order. Not required if dependencies are inferred from references.
provider Specifies the provider for a resource string Useful when multiple providers are configured. Example: provider = "aws".
lifecycle Controls lifecycle behaviors of a resource object Allows customization of creation, update, and destruction behavior. Supports ignore_changes, create_before_destroy, and prevent_destroy.
validation Validates input values for variables or attributes object Enforces constraints on variable values. Requires condition and error_message.

Example: Using count

resource "aws_instance" "web" {
  count = 3
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  tags = {
    Name = "web-${count.index}"
  }
}

Example: Using for_each

resource "aws_instance" "web" {
  for_each = {
    dev = "t2.micro"
    prod = "t3.large"
  }

  ami           = "ami-12345678"
  instance_type = each.value

  tags = {
    Name = "web-${each.key}"
  }
}

Example: Using depends_on

resource "aws_s3_bucket" "bucket" {
  bucket = "my-bucket"
}

resource "aws_s3_bucket_policy" "bucket_policy" {
  depends_on = [aws_s3_bucket.bucket]
  bucket     = aws_s3_bucket.bucket.id
  policy     = data.aws_iam_policy_document.bucket_policy.json
}

Example: Using lifecycle

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  lifecycle {
    ignore_changes = [ami]
    create_before_destroy = true
  }
}

9. Formatting Code

Example: Formatting with terraform fmt

terraform fmt
Rule Description Example
Indentation Use 2 spaces for indentation
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}
Spacing One blank line between top-level blocks
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "main" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}
Quotes Use double quotes for strings "ami-12345678"
Line Length Lines should not exceed 100 characters
variable "vpc_cidr" {
  description = "The CIDR block for the VPC"
  type        = string
}
Note: The terraform fmt command automatically formats your HCL files according to these standards.

10. Validating Syntax

Use the terraform validate command to check the syntax and structure of your Terraform configuration files.

Command Description Example
terraform validate Validates the syntax and structure of Terraform configuration files. terraform validate

Example: Validating a Terraform Configuration

terraform validate
Note: The terraform validate command checks for syntax errors and ensures that all required attributes are present. It does not interact with remote systems or resources.

11. Using Override Files

Terraform allows you to use override files to customize configuration for different environments or scenarios without modifying the main configuration files.

File Name Pattern Description Usage
terraform.tfvars Default variable file for setting values Used to override variables in root module
terraform.tfvars.json JSON format variable file Alternative to HCL format for variables
*.auto.tfvars Auto-loaded variable files Loaded automatically if present
*.auto.tfvars.json Auto-loaded JSON variable files Loaded automatically if present

Example: Using override files for environments

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
}

Example: Override variables in terraform.tfvars

ami_id        = "ami-12345678"
instance_type = "t3.micro"
Note: Override files are loaded in a specific order and can be overridden by command-line flags or environment variables.

Was this article helpful?