Skip to main content

Terraform Providers — AWS, Azure, GCP, and 3000+ More

· 5 min read
Goel Academy
DevOps & Cloud Learning Hub

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:

OperatorMeaningExampleMatches
=Exact version= 5.30.0Only 5.30.0
!=Exclude version!= 5.25.0Anything except 5.25.0
>, >=, <, <=Comparison>= 5.0.05.0.0 and above
~>Pessimistic (most common)~> 5.30>= 5.30.0, < 6.0.0
CombinedRange>= 5.0, < 5.50Between 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.

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"
}

Here is a reference table of the most commonly used providers:

ProviderSourceWhat It Manages
AWShashicorp/awsEC2, S3, RDS, Lambda, IAM, VPC, ECS, EKS, and 1000+ resources
Azurehashicorp/azurermVMs, AKS, Storage, App Service, Azure SQL, and 800+ resources
GCPhashicorp/googleCompute Engine, GKE, Cloud Storage, BigQuery, and 500+ resources
Kuberneteshashicorp/kubernetesPods, Deployments, Services, ConfigMaps, Namespaces
Helmhashicorp/helmHelm chart releases on Kubernetes clusters
GitHubintegrations/githubRepos, teams, branch protections, actions secrets
DatadogDataDog/datadogMonitors, dashboards, SLOs, downtimes
Cloudflarecloudflare/cloudflareDNS records, page rules, workers, WAF rules
Randomhashicorp/randomRandom strings, IDs, passwords, pets, shuffles
Nullhashicorp/nullNo-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:

  1. Reads required_providers to determine which providers are needed
  2. Downloads provider binaries from the registry to .terraform/providers/
  3. 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.