required_providers & Versions
Terraform configurations depend on providers — plugins that translate HCL into API calls against AWS, Azure, GCP, Kubernetes, and hundreds of other platforms. Without explicit pinning, Terraform downloads the newest provider that satisfies your constraints, which means two engineers (or two CI runs) can quietly end up on different versions. The terraform { required_providers } block makes those dependencies explicit and reproducible, and the .terraform.lock.hcl file freezes the exact versions and checksums. Getting this right is the difference between “works on my machine” and a build that behaves identically everywhere.
The required_providers block
Provider requirements live inside the top-level terraform block. Each entry maps a local name (how you refer to the provider in this configuration) to a source address and a version constraint. Modern Terraform (0.13+) requires the source attribute; relying on implicit sourcing is deprecated and unsupported in 1.x.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "aws" {
region = "us-east-1"
}
This same block works unchanged with OpenTofu, which resolves the hashicorp/* namespace from its own registry mirror.
Source addresses
A source address has three parts: [<HOSTNAME>/]<NAMESPACE>/<TYPE>. The hostname defaults to registry.terraform.io when omitted, the namespace is the organization that publishes the provider, and the type is the provider name.
| Source address | Hostname | Namespace | Type |
|---|---|---|---|
hashicorp/aws | registry.terraform.io | hashicorp | aws |
hashicorp/google | registry.terraform.io | hashicorp | |
integrations/github | registry.terraform.io | integrations | github |
app.terraform.io/acme/internal | app.terraform.io | acme | internal |
The local name (the key in
required_providers) is independent of the sourcetype. They usually match, but you can rename a provider locally — for example to disambiguate two providers that both publish atypenameddns. The local name is what you reference inproviderblocks and resourceproviderarguments.
Version constraints
Version constraints are evaluated against published semantic versions. Terraform selects the newest version that satisfies every constraint across your configuration and modules. You can combine multiple comma-separated constraints.
| Operator | Meaning | Example | Matches |
|---|---|---|---|
= (or bare) | Exact version | = 5.40.0 | only 5.40.0 |
!= | Excludes a version | != 5.41.0 | anything except 5.41.0 |
>=, <=, >, < | Comparison | >= 5.0, < 6.0 | the entire 5.x line |
~> | Pessimistic / “allow rightmost increment” | ~> 5.40 | >= 5.40, < 6.0 |
~> (patch-only) | Pin minor, allow patches | ~> 5.40.0 | >= 5.40.0, < 5.41.0 |
The ~> operator is the most common choice: ~> 5.40 lets you pick up new features and bug fixes within the 5.x major line while preventing a surprise jump to 6.0, where breaking changes are expected by semver convention.
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0, < 6.0, != 5.41.0" # avoid a known-broken patch
}
}
required_version for Terraform itself
required_version constrains the Terraform CLI version, not a provider. Use it to stop teammates running an incompatible CLI against a configuration that uses newer language features. It accepts the same constraint syntax.
terraform {
required_version = ">= 1.5.0, < 2.0.0"
}
If the running CLI falls outside the constraint, Terraform refuses to proceed:
Output:
│ Error: Unsupported Terraform Core version
│
│ This configuration does not support Terraform version 1.4.6. To proceed,
│ either choose another supported Terraform version or update this version
│ constraint. Version constraints are normally set for good reason, so
│ updating the constraint may lead to other errors or unexpected behavior.
The .terraform.lock.hcl lock file
Version constraints define a range; the dependency lock file records the exact version selected plus cryptographic checksums for every platform. Terraform creates and updates .terraform.lock.hcl during terraform init. You should commit it to version control so every run — local, CI, or production — installs byte-identical providers.
# .terraform.lock.hcl — managed by Terraform, do not edit manually
provider "registry.terraform.io/hashicorp/aws" {
version = "5.40.0"
constraints = "~> 5.40"
hashes = [
"h1:K8gQH93nWnQ+v0eHvA7jX1m1zL0pq6kxN3yQ7sQ6m0c=",
"zh:0a7e5b3...",
]
}
Running init resolves and locks the providers:
terraform init
Output:
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.40"...
- Installing hashicorp/aws v5.40.0...
- Installed hashicorp/aws v5.40.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Upgrading locked providers
The lock file is sticky: once a version is recorded, init keeps using it even if a newer one satisfies the constraint. To deliberately move forward, pass -upgrade:
terraform init -upgrade
To pre-populate hashes for every platform your team and CI use — avoiding “checksum not in lock file” failures across Linux, macOS, and Windows — record them all at once:
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=windows_amd64
Never hand-edit
.terraform.lock.hcl. If a teammate’sinitfails with a checksum mismatch, regenerate the entry withterraform providers lockrather than deleting hashes — deleting them weakens the supply-chain protection the file provides.
Best Practices
- Always set an explicit
sourceandversionfor every provider — never rely on implicit resolution. - Prefer
~>to allow patch and minor updates while blocking major-version surprises. - Add a
required_versionconstraint to keep the whole team on a compatible CLI. - Commit
.terraform.lock.hclto version control and review changes to it in pull requests. - Use
terraform providers lock -platform=...to record hashes for every OS/arch in your team and CI matrix. - Upgrade providers intentionally with
terraform init -upgrade, then test the resulting plan before merging. - Keep provider configuration (
region, credentials) inproviderblocks separate from the version-pinningrequired_providersblock.