Providers and resources are the foundation of every Terraform configuration. Providers give Terraform the ability to interact with a cloud API; resources are the actual infrastructure objects you create and manage.
Configuring Providers
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # allow 5.x but not 6.x
}
random = {
source = "hashicorp/random"
version = ">= 3.0"
}
}
}
provider "aws" {
region = "us-east-1"
# Credentials: use environment variables or IAM role — never hardcode!
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env vars are read automatically
}
# Multiple provider configurations (aliases)
provider "aws" {
alias = "eu"
region = "eu-west-1"
}
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), IAM roles, or AWS profiles instead.
Resources
A resource block declares an infrastructure object that Terraform will create and manage. The block label is resource_type.local_name:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "main-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # implicit dependency
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
Resource Arguments
- Required arguments: Must be specified (Terraform validates before plan)
- Optional arguments: Have defaults if omitted
- Computed attributes: Set by the provider after creation (e.g.,
id,arn)
Data Sources
Data sources let you query existing infrastructure that Terraform doesn't manage. Use them to reference shared resources like existing VPCs, the latest AMI ID, or a Route53 zone:
# Look up the latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
# Reference in a resource
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
}
# Look up an existing VPC by tag
data "aws_vpc" "shared" {
tags = {
Name = "shared-vpc"
}
}
resource "aws_subnet" "app" {
vpc_id = data.aws_vpc.shared.id
cidr_block = "10.1.10.0/24"
}
Resource Dependencies
Terraform automatically determines the order to create resources based on attribute references. If resource A references an attribute of resource B, Terraform creates B first:
# Terraform knows: create aws_vpc.main before aws_subnet.public
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # this reference creates the dependency
}
For dependencies that are not captured by attribute references (e.g., IAM policy must exist before an instance launches), use depends_on:
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
depends_on = [
aws_iam_role_policy_attachment.app_policy
]
}
Lifecycle Meta-Arguments
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true # fail plan if this resource would be destroyed
create_before_destroy = true # create replacement before destroying original
ignore_changes = [ # don't update if these change outside Terraform
password,
engine_version,
]
}
}
Next: Variables and Outputs — making your Terraform configurations reusable and parameterisable.