Comprehensive reference for terraform syntax reference covering essential concepts, practical examples, and production best practices.
Terraform syntax defines how infrastructure is declared, composed, and managed as code. Use this reference when writing, debugging, or reviewing .tf files—whether crafting a single resource block or orchestrating multi-environment deployments. The syntax is explicit, declarative, and idempotent: a Terraform configuration file is the source of truth for your entire cloud stack.
Define infrastructure components with resource blocks. The block’s type, name, and configuration keys are precise and tightly coupled.
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-server"
Environment = "production"
}
user_data = <<EOF
#!/bin/bash
echo "Hello from Terraform"
EOF
}
resource "type" "name": type is a provider-specific resource (e.g., aws_instance, aws_s3_bucket), name is a unique identifier in the configuration.source: aws_subnet.public.id resolves the id attribute of the aws_subnet resource named public.tags: nested object syntax with key = value pairs; keys are strings, values can be literals, expressions, or nested blocks.user_data: heredoc syntax with <<EOF and EOF delimiter; supports interpolation and multi-line scripts.count to create multiple identical resources: resource "aws_instance" "app" {
count = 3
ami = "ami-12345678"
instance_type = "t3.small"
tags = {
Name = "app-server-${count.index}"
}
}
count.index starts at 0; count.index is 0, 1, 2 for the three instances.aws_instance.app[0], aws_instance.app[1], etc.for_each for dynamic, keyed resources: resource "aws_s3_bucket" "logs" {
for_each = {
staging = "staging-logs"
production = "prod-logs"
}
bucket = each.value
tags = {
Environment = each.key
}
}
each.key is the map key ("staging", "production").each.value is the map value ("staging-logs", "prod-logs").aws_s3_bucket.logs["staging"] to reference the staging bucket.depends_on: explicitly set resource dependencies: resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
depends_on = [
aws_lambda_function.processor,
aws_iam_role.data_processor
]
}
data bucket is created only after both processor and data_processor are ready.Fetch values from external systems during configuration evaluation.
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
data "type" "name": evaluates at plan time, not just apply.filter block: repeatable; multiple filters can be defined.filter { ... }: allows nested configuration; name and values are required.most_recent: boolean; if true, picks the newest AMI matching filters.output: use data.aws_ami.ubuntu.id to access the selected AMI ID.data blocks are evaluated at plan time, so their values are frozen before terraform apply. If a data source relies on a resource that changes during apply, the data source may miss the latest state.for_each on data sources: data "aws_ami" "app_images" {
for_each = {
staging = "ami-111111"
prod = "ami-222222"
}
most_recent = true
owners = ["123456789012"]
filter {
name = "name"
values = [each.key == "staging" ? "app-staging-*" : "app-prod-*"]
}
}
each.key and each.value are available inside filter blocks.Define inputs and outputs with variable and output blocks.
variable "region" {
description = "AWS region for deployment"
type = string
default = "us-east-1"
}
variable "instance_types" {
type = map(string)
default = {
web = "t3.small"
db = "r5.large"
worker = "m5.xlarge"
}
}
output "public_ip" {
value = aws_instance.web.public_ip
description = "Public IP of the web server"
sensitive = true
}
type = string: string, number, bool, list, map, object, tuple, or set.default: can be literal or expression; supports local, data, or module values.sensitive = true: masks output value in terraform plan and terraform apply.description: appears in terraform console, terraform output, and documentation.map(string): keys are strings, values are strings.object({ ... }): variable "app_config" {
type = object({
name = string
version = string
env = string
tags = map(string)
})
default = {
name = "my-app"
version = "1.0"
env = "prod"
tags = { Team = "backend" }
}
}
list(string): ordered list of strings.Encapsulate reusable configurations with module blocks.
module "vpc" {
source = "./modules/vpc"
version = "1.2.0"
cidr_block = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
public_subnets = {
"us-east-1a" = "10.0.1.0/24"
"us-east-1b" = "10.0.2.0/24"
}
}
source: path to module directory or remote source (e.g., git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git).version: optional, resolves module version via module block.inputs: any top-level variable in the module can be assigned here.module.<name>.<output>: access module outputs, e.g., module.vpc.vpc_id.module block to pass variables: all variables in the module must be defined in the variables.tf file.source with git: module "database" {
source = "git::https://github.com/example/terraform-modules.git//db"
version = "v1.5.0"
}
// is relative to the module root.module block can be nested: module "frontend" {
source = "./modules/frontend"
app_config = {
name = "frontend"
env = "staging"
}
module "backend" {
source = "./modules/backend"
db_url = module.database.db_url
}
}
Configure provider-specific settings.
provider "aws" {
region = "us-west-2"
profile = "production"
shared_credentials_file = "/home/ubuntu/.aws/credentials"
default_tags = {
Environment = "staging"
Team = "devops"
}
}
provider "name": name is the provider type (e.g., aws, google, azurerm).region: required; sets default region for all AWS resources.profile: uses AWS profile from ~/.aws/credentials.shared_credentials_file: absolute or relative path to credentials file.default_tags: applies to all resources unless overridden.provider block can be nested inside a module: module "web" {
source = "./modules/web"
provider "aws" {
region = "us-east-1"
default_tags = {
Environment = "production"
}
}
}
web module uses its own aws provider, which takes precedence over the root aws provider.version in provider block: provider "aws" {
version = "~> 4.0"
}
~> means "compatible with", e.g., 4.0.0, 4.1.2, 4.9.9, but not 5.0.0.Set global settings in terraform block.
terraform {
required_version = ">= 1.4.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
docker = {
source = "kreuzwerker/docker"
version = ">= 2.0.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "dev/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
required_version: version constraint (e.g., ">= 1.4.0", "~> 1.0").required_providers: declares provider sources and versions.source is the registry path (e.g., hashicorp/aws).version is a constraint string.backend "type": defines state backend configuration.bucket, key, region, encrypt are standard for S3 backend.encrypt = true enables server-side encryption.terraform init must be run after changing backend configuration.backend block is evaluated at terraform init time, so key can use variables or interpolations: backend "s3" {
bucket = "my-terraform-state"
key = "prod/${local.env}/terraform.tfstate"
region = "us-east-1"
}
local.env must be defined in locals.tf or terraform.tf.Terraform uses a rich expression language.
locals {
db_name = "myapp-${var.environment}"
instance_count = length(var.instance_types)
}
resource "aws_rds_instance" "db" {
identifier = local.db_name
instance_type = var.instance_types["db"]
allocated_storage = 20
parameter_group_name = aws_db_parameter_group.custom.name
}
var.<name>: references a variable.local.<name>: references a local value.module.<name>.<output>: references a module output.data.<name>.<attribute>: references a data source attribute.aws_resource.type.name.attribute: full resource path.length(list): returns number of elements in a list.concat(list1, list2): concatenates two lists.${...}: standard interpolation: tags = {
Name = "web-${var.environment}"
}
<<EOF: heredoc for multiline strings.<% ... %>: embedded template for complex strings.Unknown resource type: resource "aws_ec2_instance" "web" fails when aws provider is not declared.Variable not defined: var.region fails if region is not declared in variables.tf.Invalid type for attribute: instance_type = 5 when instance_type expects a string.Missing required argument: resource "aws_instance" "web" fails if ami is missing.Dependency cycle: two resources depend on each other; Terraform detects it during terraform plan.Variable type mismatch: type = string but default = 42 causes error.Terraform syntax is precise and expressive. When it fails, the error messages are exact, and the configuration structure guides you directly to the source of the problem.
This page was rewritten on 10 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy