What Is a Terraform Resource?
A resource is the most important block in Terraform. Every piece of infrastructure you want to exist — an EC2 instance, an S3 bucket, a Route 53 record, a GitHub repository — is declared as a resource block. Terraform's entire job is to make the real world match what your resource blocks say.
Think of a resource block as a specification: "I want an S3 bucket named swiggy-order-images-prod with versioning enabled and public access blocked." Terraform reads that spec, compares it to what actually exists, and makes the minimum changes needed.
Resource Block Anatomy
# resource "<TYPE>" "<NAME>" — both parts together form the addressresource "aws_s3_bucket" "order_images" { # Arguments — specific to this resource type bucket = "swiggy-order-images-prod" # the S3 bucket name tags = { Environment = "production" Team = "platform" }}The resource address aws_s3_bucket.order_images is how you reference this resource from other parts of your configuration.
Referencing Resource Attributes
After Terraform creates a resource, you can read its attributes in other resource blocks:
resource "aws_s3_bucket" "order_images" { bucket = "swiggy-order-images-prod"} resource "aws_s3_bucket_versioning" "order_images" { bucket = aws_s3_bucket.order_images.id # reads the bucket ID after creation versioning_configuration { status = "Enabled" }}This reference also creates an implicit dependency — Terraform knows to create the bucket before the versioning config.
Meta-Arguments
Meta-arguments are special arguments that work on any resource type, regardless of provider:
resource "aws_instance" "app_server" { ami = "ami-0f5ee92e2d63afc18" instance_type = "t3.medium" # count — create multiple identical instances count = 3 # creates app_server[0], app_server[1], app_server[2] # depends_on — explicit dependency when there is no attribute reference depends_on = [aws_iam_instance_profile.app_profile] # lifecycle — control replace/destroy behaviour lifecycle { create_before_destroy = true # spin up replacement before killing old one prevent_destroy = true # block terraform destroy for this resource } # provider — use a non-default provider provider = aws.us_east tags = { Name = "app-server-${count.index}" }}for_each — Creating Resources from a Map
# Create one S3 bucket per environmentresource "aws_s3_bucket" "env_buckets" { for_each = toset(["dev", "staging", "prod"]) # set of strings bucket = "zerodha-data-${each.key}" # each.key = dev / staging / prod tags = { Environment = each.key }}# Creates: aws_s3_bucket.env_buckets["dev"]# aws_s3_bucket.env_buckets["staging"]# aws_s3_bucket.env_buckets["prod"]Lifecycle Rules
resource "aws_db_instance" "payments_db" { identifier = "payments-prod" engine = "postgres" instance_class = "db.t3.medium" username = "admin" password = var.db_password lifecycle { prevent_destroy = true # block accidental terraform destroy create_before_destroy = true # for zero-downtime replacement ignore_changes = [password] # don't revert manual password rotations }}Troubleshooting Resources
| Error | Cause | Fix |
|---|---|---|
Error: Reference to undeclared resource |
Typo in resource address | Check resource_type.resource_name spelling |
Error: Unsupported argument |
Wrong argument name for this resource type | Check provider documentation for valid arguments |
Error: Resource already exists |
Resource exists in AWS but not in state | Use terraform import to bring it into state |
Error: timeout while waiting for resource |
AWS API slow or limits hit | Increase timeout in timeouts block |
PLACEMENT PRO TIP**Tip:** Use `terraform state list` to see every resource Terraform is tracking. Use `terraform state show aws_s3_bucket.order_images` to inspect all attributes of a specific resource.
COMMON MISTAKE / WARNING**Common Mistake:** Using `count` when you should use `for_each`. If you have `count = 3` and remove the first item, Terraform renumbers all resources and may destroy and recreate resources you did not intend to touch. Use `for_each` with a map for any resource you might add or remove from.