Providers are Terraform's plugin system — they connect Terraform to cloud APIs. Resources are the infrastructure objects you create through those providers.
Providers
A provider is responsible for understanding API interactions with a specific service. You configure providers in your Terraform code.
Configuring a Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}Version Constraints
| Constraint | Meaning |
|---|---|
= 5.0.0 | Exactly this version |
>= 5.0 | This version or newer |
~> 5.0 | Any 5.x version (allows minor updates) |
~> 5.82.0 | Any 5.82.x version (allows patch updates) |
>= 5.0, < 6.0 | Range |
Always pin your provider versions. Unpinned providers can break your infrastructure when a new version introduces breaking changes.
Multiple Provider Instances
Deploy to multiple regions using aliases:
provider "aws" {
region = "us-east-1"
alias = "east"
}
provider "aws" {
region = "eu-west-1"
alias = "europe"
}
resource "aws_s3_bucket" "us_bucket" {
provider = aws.east
bucket = "my-app-us"
}
resource "aws_s3_bucket" "eu_bucket" {
provider = aws.europe
bucket = "my-app-eu"
}Popular Providers
| Provider | Source | Use Case |
|---|---|---|
| AWS | hashicorp/aws | EC2, S3, RDS, Lambda |
| Azure | hashicorp/azurerm | VMs, Storage, AKS |
| GCP | hashicorp/google | GCE, GCS, GKE |
| Cloudflare | cloudflare/cloudflare | DNS, CDN, Workers |
| Vercel | vercel/vercel | Frontend deployments |
| GitHub | integrations/github | Repos, teams, actions |
| Kubernetes | hashicorp/kubernetes | K8s resources |
| Docker | kreuzwerker/docker | Local containers |
Resources
Resources are the most important element in Terraform. Each resource block describes one or more infrastructure objects.
Resource Syntax
resource "provider_type" "local_name" {
argument1 = "value"
argument2 = 42
nested_block {
key = "value"
}
}- provider_type — the resource type (e.g.,
aws_instance,aws_s3_bucket) - local_name — your name for this resource (used to reference it elsewhere)
Referencing Resources
Resources export attributes that you can reference:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "web" {
vpc_id = aws_vpc.main.id # Reference the VPC's ID
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.web.id # Reference the subnet's ID
}Terraform automatically understands that the subnet depends on the VPC, and the instance depends on the subnet. It creates them in the right order.
Meta-Arguments
Every resource supports these special arguments:
resource "aws_instance" "web" {
count = 3 # Create 3 instances
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-${count.index}" # web-0, web-1, web-2
}
}
# for_each — create from a map or set
resource "aws_iam_user" "devs" {
for_each = toset(["alice", "bob", "carol"])
name = each.value
}
# depends_on — explicit dependency
resource "aws_instance" "app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
depends_on = [aws_s3_bucket.config]
}
# lifecycle — control resource behavior
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true # Zero-downtime replacement
prevent_destroy = true # Block accidental deletion
ignore_changes = [tags] # Don't track tag changes
}
}
Data Sources
Data sources let you read information from existing infrastructure that Terraform doesn't manage.
# Look up the latest Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
# Use it in a resource
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}Common data sources:
# Current AWS account info
data "aws_caller_identity" "current" {}
# Current region
data "aws_region" "current" {}
# Existing VPC by tag
data "aws_vpc" "existing" {
filter {
name = "tag:Name"
values = ["production-vpc"]
}
}
# Availability zones
data "aws_availability_zones" "available" {
state = "available"
}A Complete Example
Here's a realistic configuration that creates a VPC with a web server:
data "aws_availability_zones" "available" {
state = "available"
}
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
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = { Name = "public-subnet" }
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
resource "aws_security_group" "web" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "web-sg" }
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
EOF
tags = { Name = "web-server" }
}
output "public_ip" {
value = aws_instance.web.public_ip
}Terraform builds the dependency graph automatically:
VPC → Subnet → Instance
VPC → Internet Gateway
VPC → Security Group → InstanceSummary
- Providers connect Terraform to cloud APIs — always pin versions
- Resources describe infrastructure objects with arguments and nested blocks
- Use
countandfor_eachto create multiple similar resources - Data sources read from existing infrastructure you don't manage
- Terraform resolves dependencies automatically from resource references
- Use
lifecycleblocks to control create/destroy behavior