Skip to main content

Providers and Resources

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

ConstraintMeaning
= 5.0.0Exactly this version
>= 5.0This version or newer
~> 5.0Any 5.x version (allows minor updates)
~> 5.82.0Any 5.82.x version (allows patch updates)
>= 5.0, < 6.0Range

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"
}
ProviderSourceUse Case
AWShashicorp/awsEC2, S3, RDS, Lambda
Azurehashicorp/azurermVMs, Storage, AKS
GCPhashicorp/googleGCE, GCS, GKE
Cloudflarecloudflare/cloudflareDNS, CDN, Workers
Vercelvercel/vercelFrontend deployments
GitHubintegrations/githubRepos, teams, actions
Kuberneteshashicorp/kubernetesK8s resources
Dockerkreuzwerker/dockerLocal 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  Instance

Summary

  • Providers connect Terraform to cloud APIs — always pin versions
  • Resources describe infrastructure objects with arguments and nested blocks
  • Use count and for_each to create multiple similar resources
  • Data sources read from existing infrastructure you don't manage
  • Terraform resolves dependencies automatically from resource references
  • Use lifecycle blocks to control create/destroy behavior