Terraform Providers — AWS, Azure, GCP, and 3000+ More
Terraform by itself does not know how to create an EC2 instance, a Kubernetes pod, or a DNS record. It delegates that work to providers — plugins that translate your HCL into API calls. Understanding providers is understanding how Terraform actually talks to the outside world.
What Is a Provider?
A provider is a binary plugin that Terraform downloads during terraform init. Each provider knows how to manage a specific set of resources. The AWS provider knows EC2, S3, RDS, Lambda, and hundreds more. The Azure provider knows VMs, Storage Accounts, AKS, and so on.
Without a provider, Terraform is just a configuration language with no backend.
# The simplest possible provider configuration
provider "aws" {
region = "us-east-1"
}
This single block tells Terraform: "Download the AWS provider plugin, authenticate with AWS, and target the us-east-1 region."
The required_providers Block
Since Terraform 0.13, you must explicitly declare which providers your configuration needs. This goes in the terraform block:
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.80.0, < 4.0.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
The source tells Terraform where to download the provider from the Terraform Registry. The format is namespace/provider-name.
Version Constraints
Version constraints prevent unexpected breaking changes when a provider updates. Here are the operators you need to know:
| Operator | Meaning | Example | Matches |
|---|---|---|---|
= | Exact version | = 5.30.0 | Only 5.30.0 |
!= | Exclude version | != 5.25.0 | Anything except 5.25.0 |
>, >=, <, <= | Comparison | >= 5.0.0 | 5.0.0 and above |
~> | Pessimistic (most common) | ~> 5.30 | >= 5.30.0, < 6.0.0 |
| Combined | Range | >= 5.0, < 5.50 | Between 5.0 and 5.49.x |
The ~> operator is the one you will use 90% of the time. ~> 5.30 means "allow patch and minor updates within the 5.x series, but never jump to 6.x."
Provider Authentication
Each provider has its own authentication methods. Here are the most common patterns.
AWS — Environment Variables (Recommended for CI/CD)
provider "aws" {
region = "us-east-1"
# Credentials come from environment variables:
# AWS_ACCESS_KEY_ID
# AWS_SECRET_ACCESS_KEY
# AWS_SESSION_TOKEN (for temporary credentials)
}
AWS — Named Profile
provider "aws" {
region = "us-east-1"
profile = "production"
# Uses credentials from ~/.aws/credentials [production] section
}
Azure — Managed Identity and Service Principal
provider "azurerm" {
features {}
# For VMs/AKS with managed identity — no credentials needed
use_msi = true
# OR for service principal:
# subscription_id = var.subscription_id
# client_id = var.client_id
# client_secret = var.client_secret
# tenant_id = var.tenant_id
}
GCP — Service Account Key
provider "google" {
project = "my-gcp-project"
region = "us-central1"
# Credentials from GOOGLE_APPLICATION_CREDENTIALS env var
# Or explicitly:
# credentials = file("service-account.json")
}
Best practice: Never hardcode credentials in your .tf files. Use environment variables, IAM roles, managed identities, or a secrets manager.
Provider Aliases — Multi-Region Deployments
What if you need resources in two different AWS regions? You cannot have two provider "aws" blocks with different regions — unless you use aliases.
# Default provider — us-east-1
provider "aws" {
region = "us-east-1"
}
# Aliased provider — eu-west-1
provider "aws" {
alias = "europe"
region = "eu-west-1"
}
# This instance goes to us-east-1 (default provider)
resource "aws_instance" "us_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = { Name = "us-server" }
}
# This instance goes to eu-west-1 (aliased provider)
resource "aws_instance" "eu_server" {
provider = aws.europe
ami = "ami-0d71ea30463e0ff8d"
instance_type = "t3.micro"
tags = { Name = "eu-server" }
}
A common use case is setting up cross-region S3 replication or deploying CloudFront with ACM certificates (which must be in us-east-1):
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
resource "aws_acm_certificate" "cdn_cert" {
provider = aws.virginia
domain_name = "cdn.example.com"
validation_method = "DNS"
}
Popular Providers
Here is a reference table of the most commonly used providers:
| Provider | Source | What It Manages |
|---|---|---|
| AWS | hashicorp/aws | EC2, S3, RDS, Lambda, IAM, VPC, ECS, EKS, and 1000+ resources |
| Azure | hashicorp/azurerm | VMs, AKS, Storage, App Service, Azure SQL, and 800+ resources |
| GCP | hashicorp/google | Compute Engine, GKE, Cloud Storage, BigQuery, and 500+ resources |
| Kubernetes | hashicorp/kubernetes | Pods, Deployments, Services, ConfigMaps, Namespaces |
| Helm | hashicorp/helm | Helm chart releases on Kubernetes clusters |
| GitHub | integrations/github | Repos, teams, branch protections, actions secrets |
| Datadog | DataDog/datadog | Monitors, dashboards, SLOs, downtimes |
| Cloudflare | cloudflare/cloudflare | DNS records, page rules, workers, WAF rules |
| Random | hashicorp/random | Random strings, IDs, passwords, pets, shuffles |
| Null | hashicorp/null | No-op resources (useful for provisioners and triggers) |
Official vs Community Providers
On the Terraform Registry, you will see provider tiers:
- Official — Built and maintained by HashiCorp (e.g.,
hashicorp/aws) - Partner — Built by a technology partner, reviewed by HashiCorp (e.g.,
DataDog/datadog) - Community — Built by individuals, not officially supported
Stick with Official and Partner providers for production workloads. Community providers can disappear or go unmaintained.
What Happens During terraform init
When you run terraform init, Terraform:
- Reads
required_providersto determine which providers are needed - Downloads provider binaries from the registry to
.terraform/providers/ - Creates
.terraform.lock.hcl— a dependency lock file that pins exact versions
$ terraform init
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.82.2...
- Installed hashicorp/aws v5.82.2 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the
provider selections it made above.
The .terraform.lock.hcl file should be committed to Git. It ensures that every team member and CI pipeline uses the exact same provider version.
# Upgrade providers to the latest allowed version
terraform init -upgrade
The .terraform Directory
After init, your project directory looks like this:
my-project/
main.tf
variables.tf
outputs.tf
terraform.tfvars
.terraform.lock.hcl # Commit this
.terraform/ # Do NOT commit this
providers/
registry.terraform.io/
hashicorp/
aws/
5.82.2/
linux_amd64/
terraform-provider-aws_v5.82.2
The .terraform/ directory contains downloaded provider binaries and is often hundreds of megabytes. Never commit it to version control.
Wrapping Up
Providers are the bridge between Terraform's declarative language and real cloud APIs. Get the version constraints right, use aliases for multi-region, and never hardcode credentials. These are habits that separate a working demo from a production-grade setup.
In the next post, we will explore the Terraform resource lifecycle — understanding how create, update, destroy, and replace operations really work under the hood.
