Provider Version Constraints
Providers evolve fast. The AWS provider ships a release most weeks, tracking new AWS services and arguments almost as they land, and every few quarters it cuts a major version that carries breaking changes — renamed arguments, removed attributes, altered defaults. Constraining the provider version in required_providers is what keeps an init from silently pulling a breaking 7.0 into a config written for 6.x.
This is the same version-constraint syntax modules use, applied to the most volatile dependency in your project. The AWS provider changes far more often than Terraform core does, so the constraint here earns its keep more than almost anywhere else in the config.
Declaring Provider Constraints
The constraint lives in the required_providers block inside the terraform block. Each provider names its source — the registry address, hashicorp/aws — and a version constraint. The source is not optional cosmetics: without it Terraform guesses the namespace, which breaks for any non-HashiCorp provider and produces confusing init errors.
terraform { required_version = "~> 1.13" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } } }
Why AWS Provider Versions Matter
The AWS provider's release cadence is its own argument for pinning. A new release most weeks means an unconstrained init can pull a different version on Tuesday than it did on Monday. New AWS resources arrive tied to specific provider versions, so the feature you need may require a bump — and a major version drops support for arguments your config still uses. Both directions, too old and too new, are real risks, which is why the constraint sets a range rather than chasing latest.
The Pessimistic Constraint for Providers
The ~> operator — the pessimistic constraint — is the right tool here. ~> 6.0 allows any 6.x release, so you take patches and new minors automatically, but it refuses 7.0 and the breaking changes a major version brings. You get bug fixes and new resources within 6.x without an unreviewed jump across the major-version boundary that would land renamed or removed arguments on you mid-sprint.
# version = "~> 6.0" # 6.31.0 -> allowed (a 6.x minor) # 6.99.1 -> allowed (still within 6.x) # 7.0.0 -> blocked (breaking major, needs a reviewed bump)
Upgrading the Provider
A major version bump is a deliberate, reviewed change, not a side effect of running init. Read the provider's upgrade guide first — it lists the renamed and removed arguments — then widen the constraint to the new major, run init -upgrade to move the lock, and read the resulting plan carefully for replacements before applying. Treating the bump as a normal PR, with the plan attached, is what keeps a 6-to-7 move from breaking production by surprise.
# 1. read the v7 upgrade guide on the registry # 2. widen the constraint: version = "~> 7.0" # 3. rewrite the lock within the new range terraform init -upgrade # 4. review the plan for renamed args and replacements terraform plan
required_version vs Provider Version
Two separate constraints guard two separate things. required_version pins Terraform core — the binary that runs the plan — and the provider's version pins the AWS plugin. Constraining only the provider leaves core version skew in place, where one teammate runs 1.7 and another 1.9 and a state file gets written by a version others cannot read. Pin both, in the same terraform block, so the whole team and CI run identical core and identical provider.
- Leaving the AWS provider unconstrained, so a routine
initjumps to a new major version mid-sprint and breaks the config on renamed arguments. - Pinning to an exact old provider version forever, losing access to new AWS resources and the bug fixes that ship in later 6.x releases.
- Bumping a major provider version without reading its upgrade guide, then hitting renamed or removed arguments only after the plan errors.
- Constraining the provider but omitting
required_versionfor Terraform core, so version skew across the team persists and writes an unreadable state. - Using
>= 6.0instead of~> 6.0, which permits 7.0 and reintroduces exactly the unreviewed major jump the constraint was meant to block.
- Constrain the AWS provider with
~>and commit the lock file, so everyone resolves the same version within an allowed range. - Read the provider's upgrade guide before any major bump and do the move as a reviewed change with the plan attached.
- Keep both
required_versionfor core and the providerversionconstraint in place, in the sameterraformblock. - Upgrade regularly in small steps rather than letting the provider fall years behind and turning the eventual jump into a migration project.
- Always declare an explicit
sourcefor every provider, so Terraform never guesses the namespace and fails on non-HashiCorp providers.
Knowledge Check
What does the constraint ~> 6.0 on the AWS provider allow and block?
- It allows any 6.x release but blocks 7.0 and its breaking changes
- It pins exactly 6.0.0 and rejects every later patch or minor release
- It allows any version 6.0 or higher, including the 7.x major line
- It allows only new major versions and blocks every minor release
Why is the AWS provider version especially worth constraining?
- It ships a release most weeks and cuts breaking major versions periodically
- It is the only provider Terraform ever downloads during the init step
- Its release versions are chosen by AWS itself and cannot be pinned at all
- It almost never changes, so the constraint is purely documentation
Why is pinning to an exact old provider version forever also a risk?
- You lose access to new AWS resources and bug fixes shipped in later releases
- Terraform simply refuses to run against any exactly pinned provider version
- An exact pin forces a breaking major upgrade on the very next init regardless
- The lock file is unable to record an exactly pinned provider version
What is the difference between required_version and a provider version constraint?
required_versionpins Terraform core; the providerversionpins the AWS plugin- They are two aliases for the same single setting and only one is ever needed
required_versionpins the provider plugin whileversionpins the lock file- Both of them pin Terraform core, so the provider version is left unconstrained
You got correct