Skip to main content

Resources

Resources are the most important element in Terraform. Each resource block describes one or more infrastructure objects, such as virtual machines, storage buckets, or DNS records.

Resource Syntax

A resource block declares a resource of a specific type with a specific local name:
resource "<RESOURCE_TYPE>" "<NAME>" {
  # Configuration arguments
}

Example

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}
The resource type (aws_instance) and name (web) together form a unique identifier: aws_instance.web.

Resource Types

Each provider defines resource types. The type name format is:
<PROVIDER>_<TYPE>
Examples:
  • aws_instance - AWS provider, instance resource
  • azurerm_virtual_machine - Azure provider, virtual machine resource
  • google_compute_instance - Google Cloud provider, compute instance resource

Resource Arguments

Provider-Specific Arguments

Each resource type has its own set of required and optional arguments:
resource "aws_instance" "web" {
  # Required
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  # Optional
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [aws_security_group.web.id]
  
  tags = {
    Name        = "WebServer"
    Environment = "production"
  }
}

Nested Blocks

Many resources support nested configuration blocks:
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  network_interface {
    device_index = 0
    description  = "Main network interface"
  }
}

Meta-Arguments

Meta-arguments change resource behavior and work with any resource type.

count

Create multiple instances using an integer:
resource "aws_instance" "web" {
  count = 3
  
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  tags = {
    Name = "web-${count.index}"
  }
}
Reference instances:
# All instances
all_ips = aws_instance.web[*].private_ip

# Specific instance
first_ip = aws_instance.web[0].private_ip

for_each

Create instances from a map or set:
resource "aws_instance" "web" {
  for_each = {
    web1 = "t2.micro"
    web2 = "t2.small"
  }
  
  ami           = "ami-12345678"
  instance_type = each.value
  
  tags = {
    Name = each.key
  }
}
Reference instances:
web1_ip = aws_instance.web["web1"].private_ip
A resource cannot use both count and for_each - they are mutually exclusive.

depends_on

Explicitly declare dependencies:
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  depends_on = [
    aws_security_group.firewall,
  ]
}
Terraform automatically determines most dependencies. Use depends_on only when necessary.

provider

Select a non-default provider configuration:
resource "aws_instance" "web" {
  provider = aws.west
  
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

lifecycle

Customize resource lifecycle behavior:
resource "aws_security_group" "firewall" {
  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    
    ignore_changes = [
      description,
    ]
  }
}

Lifecycle Arguments

create_before_destroy
boolean
Create replacement before destroying the original. Useful for zero-downtime updates.
prevent_destroy
boolean
Prevent accidental resource destruction. Terraform will error if you try to destroy this resource.
ignore_changes
list
Ignore changes to specified attributes. Use all to ignore all changes.
ignore_changes = [ami, instance_type]
# or
ignore_changes = all
replace_triggered_by
list
Replace this resource when specified resources change:
lifecycle {
  replace_triggered_by = [
    aws_instance.web[1],
    aws_security_group.firewall.id
  ]
}

Provisioners

Provisioners run scripts or commands during resource creation or destruction:
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }
  
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx",
    ]
  }
}
Provisioners are a last resort. Use them only when no other option exists. Consider cloud-init, user data, or configuration management tools instead.

Provisioner Types

  • local-exec - Run commands on the machine running Terraform
  • remote-exec - Run commands on the remote resource
  • file - Copy files to the remote resource

Destroy-Time Provisioners

provisioner "local-exec" {
  when    = destroy
  command = "./cleanup.sh ${self.id}"
}

Connection Blocks

Configure how provisioners connect to resources:
resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
  
  connection {
    type        = "ssh"
    host        = self.public_ip
    user        = "ubuntu"
    private_key = file("~/.ssh/id_rsa")
  }
  
  provisioner "remote-exec" {
    inline = ["echo hello"]
  }
}

Preconditions and Postconditions

Validate assumptions about resources:
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
  
  lifecycle {
    precondition {
      condition     = var.instance_type != "t2.nano"
      error_message = "Instance type cannot be t2.nano."
    }
    
    postcondition {
      condition     = self.private_ip != ""
      error_message = "Instance must have a private IP."
    }
  }
}

Resource Addresses

Reference resources in your configuration:
# Simple resource
aws_instance.web

# Resource with count
aws_instance.web[0]
aws_instance.web[*]  # All instances

# Resource with for_each
aws_instance.web["primary"]

# Resource attributes
aws_instance.web.id
aws_instance.web.private_ip

Resource Behavior

Create

Terraform creates resources that don’t exist:
terraform apply

Update

Terraform updates resources when arguments change:
resource "aws_instance" "web" {
  instance_type = "t2.small"  # Changed from t2.micro
}

Replace

Some changes require replacement (destroying and recreating):
resource "aws_instance" "web" {
  ami = "ami-87654321"  # AMI change requires replacement
}

Destroy

Remove resources from configuration:
terraform destroy

Data Sources

Read information about existing infrastructure:
data "aws_ami" "ubuntu" {
  most_recent = true
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
  
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
  
  owners = ["099720109477"]
}

resource "aws_instance" "web" {
  ami = data.aws_ami.ubuntu.id
}

Ephemeral Resources

Short-lived resources that exist only during operations:
ephemeral "aws_secret" "auth" {
  for_each = local.auths
  input    = each.value
  
  depends_on = [aws_instance.depends]
}
Ephemeral resources are not stored in state and exist only during Terraform operations.

Best Practices

Use Descriptive Names

Choose clear, descriptive names for resources that indicate their purpose.

Minimize Provisioners

Avoid provisioners when possible. Use native resource features instead.

Explicit Dependencies

Let Terraform infer dependencies. Use depends_on only when necessary.

Lifecycle Management

Use lifecycle arguments to prevent accidental destruction of critical resources.

Next Steps

Providers

Configure providers for resources

Variables

Parameterize resource configurations

Build docs developers (and LLMs) love