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" }}<PROVIDER_RESOURCE_TYPE>— the resource type from the provider (e.g.,aws_instance,azurerm_virtual_machine,google_compute_instance)<LOCAL_NAME>— a local identifier used to reference this resource elsewhere in the configuration
Common AWS Resource Examples
# EC2 Instanceresource "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 configurationresource "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 applyresource "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"].idprovider
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).