Terraform Resources
Resources are the most important building blocks in Terraform. They describe infrastructure objects that Terraform will create, update, or delete. Every infrastructure component in Terraform is a resource.
What is a Resource?
A resource represents a real-world infrastructure component:
- Virtual Machines (AWS EC2, Azure VM, Google Compute)
- Storage (AWS S3, Azure Blob, Google Cloud Storage)
- Networking (VPCs, subnets, security groups, load balancers)
- Databases (RDS, Cosmos DB, Cloud SQL)
- Container Services (ECS, AKS, GKE)
- DNS (Route53, Azure DNS, Cloud DNS)
- IAM (Users, roles, policies)
Resource Syntax
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "WebServer"
Environment = "dev"
}
}Components:
- Type:
aws_instance(provider + resource type) - Local Name:
web_server(reference in code) - Arguments: Configuration properties (ami, instance_type, tags)
Reference in other resources:
# Reference the instance
resource "aws_security_group" "web" {
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web" {
# ...
vpc_security_group_ids = [aws_security_group.web.id]
}Common AWS Resources
| Resource | Purpose | Example |
|---|---|---|
| aws_instance | EC2 virtual machine | Web server, app server |
| aws_vpc | Virtual network | Network isolation |
| aws_subnet | Network segment | Public/private subnets |
| aws_security_group | Firewall rules | Port/protocol access |
| aws_s3_bucket | Object storage | Static files, backups |
| aws_rds_instance | Managed database | MySQL, PostgreSQL |
| aws_iam_role | Access role | Service permissions |
| aws_lambda_function | Serverless function | Event handlers |
| aws_api_gateway_rest_api | API endpoint | REST API gateway |
| aws_internet_gateway | Internet access | Route to internet |
Arguments, Attributes, and Meta-Arguments
Arguments (inputs):
resource "aws_instance" "web" {
ami = "ami-123456" # Argument
instance_type = "t2.micro" # Argument
associate_public_ip_address = true # Argument
}Attributes (outputs from state):
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
}
# Access attributes
output "instance_ip" {
value = aws_instance.web.public_ip # Attribute
}Meta-Arguments (control resource behavior):
resource "aws_instance" "servers" {
count = 3 # Create 3 instances
ami = "ami-123456"
instance_type = "t2.micro"
depends_on = [aws_security_group.web] # Explicit dependency
provider = aws.us_west_2 # Specific provider
}Resource Dependencies
Implicit Dependencies:
resource "aws_instance" "web" {
# Automatically depends on security group
vpc_security_group_ids = [aws_security_group.web.id]
}Explicit Dependencies:
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
# Must create security group first
depends_on = [aws_security_group.web]
}
resource "aws_security_group" "web" {
vpc_id = "vpc-123456"
}Resource Lifecycle
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t2.micro"
lifecycle {
create_before_destroy = true # New before destroying old
ignore_changes = [tags] # Don't track tag changes
replace_triggered_by = [aws_security_group.web] # Replace if SG changes
}
}Options:
create_before_destroy: Create new before destroying oldprevent_destroy: Error if trying to destroyignore_changes: Don't update if these changereplace_triggered_by: Recreate if specific resource changes
Data Sources
Data sources fetch information about existing infrastructure that was created outside Terraform or looked up from external systems. They are read-only.
Data Source Syntax
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
# Use the data
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}Common Data Sources
| Data Source | Purpose | Example |
|---|---|---|
| aws_ami | Find AMI image | Latest Ubuntu, Amazon Linux |
| aws_availability_zones | List AZs in region | Multi-AZ setup |
| aws_vpc | Get existing VPC | Reference existing VPC |
| aws_subnets | List subnets | Network setup |
| aws_security_group | Get security group | Existing firewall rules |
| aws_rds_cluster | Fetch RDS info | Database endpoints |
| aws_kubernetes_cluster | Get cluster details | API endpoint |
| aws_caller_identity | Current AWS account | Account ID, ARN |
| http | Fetch HTTP content | External APIs |
| local_file | Read local files | SSH keys, configs |
Finding Available Data
Documentation:
- Registry: registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/
List with CLI:
terraform providers schema -json | jq '.provider_schemas."registry.terraform.io/hashicorp/aws".data_source_schemas | keys'Building Fetch Filters
# Get latest Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-*"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"]
}Using Data Sources with Resources
Pattern: Fetch data, then use in resource
# Step 1: Find VPC by tag
data "aws_vpc" "main" {
filter {
name = "tag:Name"
values = ["production"]
}
}
# Step 2: Use VPC in security group
resource "aws_security_group" "web" {
vpc_id = data.aws_vpc.main.id
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Step 3: Use security group in instance
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.web.id]
}Local Data Source
Read local files:
data "local_file" "ssh_key" {
filename = "${path.module}/ssh/id_rsa.pub"
}
resource "aws_key_pair" "default" {
key_name = "my-key"
public_key = data.local_file.ssh_key.content
}HTTP Data Source
Fetch external data:
data "http" "cloud_ips" {
url = "https://api.github.com/meta"
}
locals {
github_hooks_ips = jsondecode(data.http.cloud_ips.response_body).hooks
}Data vs Resources
| Aspect | Resource | Data Source |
|---|---|---|
| Creates infrastructure | Yes | No |
| Read from state | Writing | Reading |
| Destroyed on destroy | Yes | No |
| Used for | Creating objects | Querying info |
| Direction | Managed by TF | Read-only |
| Example | aws_instance | aws_ami |
Locals vs Data Sources
# Locals: Calculate/define values
locals {
common_tags = {
Environment = "prod"
Team = "platform"
}
}
# Data sources: Fetch existing infrastructure
data "aws_availability_zones" "available" {
state = "available"
}
# Use both
resource "aws_instance" "web" {
availability_zone = data.aws_availability_zones.available.names[0]
tags = merge(
local.common_tags,
{ Name = "web-server" }
)
}Best Practices
- Use data sources for existing infrastructure — Don't hardcode IDs
- Organize resources logically — Group by type or component
- Use explicit dependencies — When implicit ones miss order
- Document complex resources — Explain why certain arguments are set
- Use lifecycle rules — Prevent accidental destruction in production
- Fetch latest versions — Use most_recent in data sources when appropriate
- Reference, don't copy — Use resource references instead of hardcoding
- Validate data source results — Check if data exists before using