Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Terraform Resource Blocks

Resource blocks are the heart of every Terraform configuration. Each resource block describes one infrastructure object — an EC2 instance, S3 bucket, Kubernetes deployment, DNS record, or any other object managed by a provider.


Resource Block Syntax

resource "<PROVIDER_RESOURCE_TYPE>" "<LOCAL_NAME>" {
# Configuration arguments
argument_one = "value"
argument_two = 42
# Nested blocks
nested_block {
setting = "value"
}
}

Common AWS Resource Examples

# EC2 Instance
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
iam_instance_profile = aws_iam_instance_profile.web.name
key_name = var.key_pair_name
root_block_device {
volume_type = "gp3"
volume_size = 30
encrypted = true
delete_on_termination = true
}
user_data = base64encode(templatefile("${path.module}/scripts/init.sh", {
environment = var.environment
}))
tags = merge(local.common_tags, {
Name = "${var.environment}-web-server"
})
}
# S3 Bucket with complete configuration
resource "aws_s3_bucket" "app_data" {
bucket = "${var.environment}-${var.service_name}-data"
tags = local.common_tags
}
resource "aws_s3_bucket_versioning" "app_data" {
bucket = aws_s3_bucket.app_data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "app_data" {
bucket = aws_s3_bucket.app_data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}

Referencing Resource Attributes

Resource attributes are referenced with <resource_type>.<local_name>.<attribute>:

resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # Reference VPC ID
cidr_block = "10.0.1.0/24"
}
resource "aws_security_group" "web" {
vpc_id = aws_vpc.main.id # Same reference
name = "web-sg-${aws_vpc.main.id}" # In string interpolation
}
# Some attributes are only known after apply
resource "aws_instance" "app" {
# ...
}
output "app_public_ip" {
value = aws_instance.app.public_ip # Known after apply
}

Meta-Arguments

Meta-arguments apply to all resource types regardless of provider:

depends_on

resource "aws_instance" "app" {
ami = data.aws_ami.latest.id
instance_type = "t3.micro"
# Explicit dependency — wait for IAM role policy to be attached before creating instance
depends_on = [aws_iam_role_policy_attachment.app]
}

count

resource "aws_instance" "workers" {
count = 3
ami = data.aws_ami.latest.id
instance_type = "t3.micro"
tags = {
Name = "worker-${count.index + 1}"
}
}
# Reference: aws_instance.workers[0], aws_instance.workers[1], etc.

for_each

resource "aws_s3_bucket" "environments" {
for_each = toset(["dev", "staging", "production"])
bucket = "mycompany-${each.key}-data"
tags = { Environment = each.key }
}
# Reference: aws_s3_bucket.environments["production"].id

provider

resource "aws_s3_bucket" "west_coast" {
provider = aws.us_west # Use aliased provider
bucket = "my-west-bucket"
}

lifecycle Meta-Argument

resource "aws_db_instance" "production" {
identifier = "prod-postgres"
engine = "postgres"
instance_class = "db.r6g.large"
allocated_storage = 100
lifecycle {
# Don't destroy this resource — even on terraform destroy
prevent_destroy = true
# Create replacement before destroying (zero-downtime updates)
create_before_destroy = true
# Ignore external changes to these attributes
ignore_changes = [
password, # Rotated by secrets manager
engine_version, # Managed by RDS automated minor version updates
latest_restorable_time # Changes constantly, not relevant
]
# Add conditions that must be true for apply to proceed
precondition {
condition = var.environment == "production"
error_message = "This database is for production only."
}
postcondition {
condition = self.status == "available"
error_message = "Database did not reach available status."
}
}
}

Dynamic Nested Blocks

When the number of nested blocks isn’t known until runtime:

variable "ingress_rules" {
type = list(object({
port = number
protocol = string
description = string
cidr = string
}))
default = [
{ port = 80, protocol = "tcp", description = "HTTP", cidr = "0.0.0.0/0" },
{ port = 443, protocol = "tcp", description = "HTTPS", cidr = "0.0.0.0/0" },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
description = ingress.value.description
cidr_blocks = [ingress.value.cidr]
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "All outbound"
}
}

Resource Timeouts

resource "aws_db_instance" "main" {
# ...
timeouts {
create = "60m" # Wait up to 60 minutes for creation
update = "80m" # Wait up to 80 minutes for updates
delete = "40m" # Wait up to 40 minutes for deletion
}
}

Default timeouts vary by resource — check the provider documentation. Increase for resources that take a long time (large RDS restores, complex EKS upgrades).