Terraform configuration: quick reference

This page contains a quick reference for writing Terraform configuration. For a conceptual introduction to Terraform and managing your infrastructure as code, read this blog post.

Disclaimer: for the most up-to-date and detailed information, check out the official Terraform documentation.

Overview - Configuration Language | Terraform by HashiCorp
You can use the Terraform language to write configuration files that tell Terraform how to manage a collection of infrastructure.

Jump to:

Configuration blocks

Terraform

Configuration block that allows you to specify various Terraform settings (such as required versions for Terraform and/or its providers).

terraform {
  required_providers {
    # e.g. include the AWS provider and require a specific major version
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.0.0, < 5.0.0"
    }
  }
  required_version = ">= 1.2.3"
}

Provider

Versioned code which is used to communicate with an external infrastructure provider.

provider "provider_name" {
  # provider-specific configuration
  # e.g. what region should this provider create your resources in?
} 

Resource

Smallest unit of infrastructure managed by a provider.

resource "resource_type" "resource_name" {
  # resource-specific configuration
}
  • The resource_type always starts with the prefix {provider}_
  • You can reference a given resource in other parts of your Terraform configuration using the format {resource_type}.{resource_name}
  • There's a small set of meta-arguments which you can use for any resource:
    • count: create multiple instances of the same resource specification
    • for_each: configure a set of resources based on a provider iterable
    • provider: override the default provider configuration (e.g. changing the region to deploy a specific resource)
    • depends_on: specify hidden dependencies between resources that Terraform isn't able to infer
    • lifecycle: configuration around resource lifecycle management (e.g. create a new version of the resource before destroying the old one)

Variable

Allow end users to specify values (to be used in resource creation) when defining a module.

variable "variable_name" {
  type        = string
  description = ""
  
  # optional
  default     = ""
  sensitive   = true
  validation {
    condition     = ...
    error_message = ""
  }
}
  • Usually defined in a separate file, variables.tf, for readability
  • These values are constant, they cannot be changed during planning or execution
  • You can reference a given variable as var.{variable_name}
  • Supported types:
    • simple (string, bool, number)
    • collection (map, list, set)
    • structural (tuple, object)
  • You can optionally add one or more validation conditions (e.g. to make sure a resource matches AWS resource naming requirements). This can be helpful for "failing fast" when you provide improper values.
  • By default, variables are not treated as sensitive. You can specify sensitive = true in order to prevent Terraform from outputing these values to the console.

Output

Expose a specific attribute of a resource within a module.

output "output_name" {
  description = ""
  value       = resource_type.resource_name.attribute
  
  # optional
  sensitive   = true
  precondition {
    condition     = ...
    error_message = ""
  }
}
  • Usually defined in a separate file, outputs.tf, for readability
  • You can reference a module's output as {module_name}.{output_name}
  • You can optionally add one or more preconditions to check before returning an output value (e.g. to check that a certificate status is set to ISSUED). This can be used as a last line of defense to validate your assumptions before returning data about a given resource.
  • By default, variables are not treated as sensitive. You can specify sensitive = true in order to prevent Terraform from outputing these values to the console.

Module

A logical grouping of resources which can be configured and deployed together.

module "module_name" {
  source          = "../path/to/module"
  
  # optional
  version = ""
}
  • Encourages reuse of configuration and provides consistency across your infrastructure stack
  • There’s a small set of universal “meta-arguments” that you can use for any module:
    • count: create multiple instances of the same module specification
    • for_each: configure a set of modules based on a provider iterable
    • provider: override the default provider configuration (e.g. changing the region to deploy the resources in your module)
    • depends_on: specify hidden dependencies between your module and other resources that Terraform isn't able to infer
  • Modules can be referenced from local filepaths or remote repositories (Terraform registry, Github, S3)

In addition to using existing modules available on the Terraform registry, you can also define your own. A module can be defined as a set of Terraform configuration files within a single directory.

.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
An example Terraform module definition.

Data

Retrieve information from an external infrastructure provider.

data "data_resource_type" "data_resource_name" {
  # parameters used to retrieve information
}

Locals

Used to provide a succinct or readable name for a Terraform expression which may be referenced multiple times.

locals {
  # e.g. define a set of required tags to add to all resources
  required_tags = {
    project     = var.project_name,
    environment = var.environment
  }
  tags = merge(var.resource_tags, local.required_tags)
  
  # e.g. define a common suffix for use in multiple resource definitions
  name_suffix = "${var.project_name}-${var.environment}"
}
  • Can be referenced as locals.{attribute}
  • Dynamic expressions (e.g. using Terraform functions) are allowed, providing more flexibility than you have available in Terraform variables
  • It's recommended you use locals sparringly, keeping in mind the trade-offs between readability and DRY principles
    • In other words, don't make someone constantly scroll up to the locals block in order to figure out how a given resource is being configured

Dynamic expressions

Conditional statements

You can express conditional statements in Terraform, although I find the syntax a bit odd.

attribute  = (conditional_expression ? value_if_true : value_if_false)

Interpret ? as “then” and : as “else” when reading.

The conditional_expression itself is defined with common syntax:

Symbol Meaning
!= not equal
== equal

Wildcards

You can use a splat * expression to reference a given attribute over a list of resources.

resource "aws_instance" "app_servers" {
  count = 5
  ...
}

output "private_addresses" {
  value = aws_instance.app_servers[*].private_dns
}

Comprehensions

You can create lists and maps using syntax similar to list/dictionary comprehensions in Python.

The general syntax is [for item in iterable: value] for a list and {for item in iterable: key => value} for a map. You can also add conditional statements, such as in the example below.

locals {
  tags = tomap({ foo = "123", bar = "456", empty = "" })
}

value = { for key, value in local.tags : key => value if value != "" }