Constraints and the Lock File
Scenario: Two engineers run the exact same .tf files on the exact same day and end up planning against two different provider releases, producing two different plans for what should be an identical change.
A version constraint (like ~> 1.0 in a required_providers block) defines a range of acceptable provider versions - it's a policy, not a specific choice. The dependency lock file (.terraform.lock.hcl, which init creates or updates) is different: it records the one exact version that was actually selected the last time someone ran init, plus cryptographic checksums to detect tampering or corruption. Constraint syntax is worth memorizing precisely because it directly controls how much drift is possible between two engineers' machines: ~> 1.4 (the "pessimistic constraint operator") permits any 1.x release from 1.4 upward but forbids 2.0, protecting you from an unexpected breaking major version; a bare >= 1.0 with no upper bound offers no such protection and can silently pull in a breaking 2.0 release on someone's next init -upgrade.
Always commit .terraform.lock.hcl to version control - it's the mechanism that makes "two engineers, same code, same provider version" actually true. Update it intentionally, by running terraform init -upgrade, then reviewing the diff and re-testing, rather than letting it drift silently. Note required_version (for the Terraform CLI itself) and provider version constraints are two separate settings solving the same kind of problem at two different layers.
Analogy: A version constraint is the list of ingredients a restaurant is willing to accept from any supplier ("any brand of tomato, as long as it's a certain grade"); the lock file is the receipt naming the exact batch, from the exact supplier, actually used in tonight's service. Two kitchens following the same acceptable-ingredients policy can still cook with different tomatoes unless they share the receipt.
A worked configuration
terraform {
required_version = ">= 1.8, < 2.0"
required_providers {
fakecloud = {
source = "shellgenius/fakecloud"
version = "~> 1.0"
}
}
}
In network-restricted environments (locked-down CI runners, air-gapped builds), teams often run a private provider mirror and verify checksums explicitly rather than reaching the public registry every time. Lock file entries are also platform-aware (they record checksums per OS/architecture), so a team whose engineers use different laptop architectures than their CI runner may need to deliberately populate multi-platform checksums, not just whatever their own machine produced.
Common mistakes in production
- Using an unbounded
>=constraint on a provider "to always get the latest features" - this removes the protection against unannounced breaking changes that the lock file and pessimistic constraints exist to provide. - Not committing the lock file (or .gitignoring it by mistake) - every engineer and every CI run can then silently select a different provider version.
Goal: Do not delete.terraform.lock.hcljust to make an installation error go away. That error is usually telling you something real (a checksum mismatch, an unreachable mirror) - deleting the file hides the symptom while leaving every collaborator free to select a different version from that point on. Reinforce this lesson in the tf-version-lockfile Terraform lab. Open/labs/terraformand choose slugtf-version-lockfile; the lab runs real Terraform against the offline FakeCloud provider.