Providers, Versions & the Lock File
By the end of this lesson you will know how Terraform finds and downloads providers, how to pin their versions so builds stay stable, what .terraform.lock.hcl protects you from, and how to use two copies of one provider for two regions.
What a provider is
Terraform itself knows nothing about AWS, Azure or GitHub. All of that knowledge lives in providers: separate plugin programs that Terraform downloads and runs. Each provider offers a set of resource types (such as aws_instance) and data sources (such as aws_ami).
Think of Terraform as a power drill and providers as drill bits. The drill is the same; you fit a different bit for wood, metal or concrete.
Providers are published on the Terraform Registry (registry.terraform.io). They come in three tiers:
- Official: maintained by HashiCorp, such as
hashicorp/aws,hashicorp/randomandhashicorp/local. - Partner: maintained by the company behind the service, such as
cloudflare/cloudflare. - Community: maintained by individuals or groups. Many are excellent, but check activity and issues before trusting one.
Source addresses
Every provider has a source address in the form namespace/type. The full form includes the registry host, which defaults to registry.terraform.io:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.9"
}
}
}The key on the left (aws, random) is the local name you use in the rest of the configuration. Keep it the same as the type unless you have a strong reason.
To see which providers a folder needs, run:
terraform providersProviders required by configuration: . ├── provider[registry.terraform.io/hashicorp/aws] ~> 6.0 └── provider[registry.terraform.io/hashicorp/random] ~> 3.9
The dot at the top means "this folder". Modules you call would appear as branches below it.
Version constraints
Providers release often. A new major version can rename or remove arguments, so you pin the versions you accept. Constraint operators:
| Constraint | Meaning |
|---|---|
= 6.71.0 | Exactly this version |
>= 6.0 | This version or anything newer, including 7.x |
>= 6.0, < 7.0 | Any 6.x version |
~> 6.0 | Any 6.x version (only the last number may grow) |
~> 6.71.0 | 6.71.x only, so only patch releases |
!= 6.50.0 | Any version except this broken one |
The ~> operator is called the pessimistic constraint. ~> 6.0 allows 6.1, 6.71 and so on, but never 7.0. That is usually what you want for providers: new features and fixes, but no surprise breaking changes.
You can also pin Terraform itself with required_version:
terraform {
required_version = ">= 1.14"
}If a teammate runs an older Terraform, they get a clear error instead of strange behaviour.
The dependency lock file
Constraints say what is allowed. The lock file, .terraform.lock.hcl, records what was actually chosen, plus checksums of the downloaded files. terraform init creates it the first time:
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "6.71.0"
constraints = "~> 6.0"
hashes = [
"h1:...",
"zh:...",
]
}Because the file is committed to Git, everyone on the team and the CI pipeline get exactly 6.71.0, even if 6.72.0 comes out tomorrow. The hashes also prove that the downloaded plugin was not tampered with. It works like the seal on a medicine bottle from a chemist in Pune: if the seal does not match, you do not use it.
Upgrading providers safely
Upgrades happen only when you ask. Follow these steps.
Step 1: read the provider's changelog for the versions between yours and the newest.
Step 2: run init with the upgrade flag. It picks the newest versions that your constraints allow and rewrites the lock file:
terraform init -upgradeInitializing provider plugins... - Finding hashicorp/aws versions matching "~> 6.0"... - Installing hashicorp/aws v6.72.0... - Installed hashicorp/aws v6.72.0 (signed by HashiCorp) Terraform has made some changes to the provider dependency selections recorded in the .terraform.lock.hcl file. Review those changes and commit them to your version control system if they represent changes you intended to make.
The output is trimmed, and the version numbers are an example of what you might see.
Step 3: run terraform plan. A provider upgrade should usually produce no changes. If it does show changes, read them carefully before applying.
Step 4: commit the updated lock file in its own pull request, so reviewers know the change is an upgrade.
Configuring a provider
Most providers need settings. For AWS, the important one is the region. Mumbai is ap-south-1 and Hyderabad is ap-south-2:
provider "aws" {
region = "ap-south-1"
}Never put access keys in this block. The AWS provider finds credentials the same way the AWS CLI does: environment variables, a named profile in ~/.aws/config, single sign-on, or an IAM role when it runs inside AWS. The IAM Roles, Identity Center & Least Privilege lesson explains the safe options.
Aliases: two copies of one provider
Sometimes you need the same provider twice with different settings. For example, Kabir's team keeps backups in Hyderabad and the main app in Mumbai. Give the second block an alias, then choose it per resource with the provider argument:
provider "aws" {
region = "ap-south-1"
}
provider "aws" {
alias = "hyderabad"
region = "ap-south-2"
}
resource "aws_s3_bucket" "backups" {
provider = aws.hyderabad
bucket = "kabir-backups-example"
}Resources without a provider argument use the default (un-aliased) configuration.
Common mistakes
- No version constraint. Without one, the next
initon a fresh machine may pull a new major version and break your plan. - Not committing the lock file. Your laptop and the CI pipeline then use different provider versions.
- Hard-coding credentials in the provider block. They end up in Git and in history forever.
- Upgrading many providers at once. If something breaks, you will not know which upgrade caused it.
Key points
- Providers are plugins that teach Terraform about one platform; they are listed in
required_providers. - A source address like
hashicorp/awspoints to the Terraform Registry. - Use
~>constraints to allow fixes but block surprise major upgrades; userequired_versionfor Terraform itself. .terraform.lock.hclrecords exact versions and checksums; commit it.- Upgrade on purpose with
terraform init -upgrade, then check thatplanshows no unexpected changes. - Use
aliasand theproviderargument to use two configurations of one provider.
Exercise
Write a terraform block for a project that needs Terraform 1.14 or newer, any 3.x version of hashicorp/random, and only patch releases of hashicorp/local 2.9. Then add a default AWS provider for Mumbai and an aliased one called hyderabad for the Hyderabad region.
Show solution
terraform {
required_version = ">= 1.14"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
local = {
source = "hashicorp/local"
version = "~> 2.9.0"
}
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-south-1"
}
provider "aws" {
alias = "hyderabad"
region = "ap-south-2"
}terraform init~> 3.0 allows any 3.x release, while ~> 2.9.0 allows only 2.9.x patches. AWS also needs a required_providers entry because the configuration uses it.
Quick Quiz
1. What does the constraint ~> 6.0 allow?
The pessimistic constraint lets only the last given number grow.
2. What is the main job of .terraform.lock.hcl?
The dependency lock file pins provider selections; state locking is a different feature.
3. How do you make one resource use an aliased provider called aws.hyderabad?
The provider meta-argument chooses a non-default provider configuration.
Learn Terraform & Ansible with a mentor
Free lessons get you started. Our Cloud & DevOps training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
