Terraform declares an allowed range of provider versions through required_providers constraints, and pins the exact versions actually used through .terraform.lock.hcl. Once you cleanly separate the two roles, you can prevent accidental upgrades in CI while still allowing planned updates.
This article covers how to write constraints, when to refresh the lock file, tips for CI and team operations, and how to handle common errors — clarifying the points the Associate exam likes to ask about.
required_providers declares which providers are allowed, from which source, and within which version range. It is purely a range specification, not a pin to a single exact version.
.terraform.lock.hcl is created and updated when you run terraform init, and pins the exact selected version along with checksums. While the lock file exists, a normal init honors its contents and never silently upgrades — updates require an explicit -upgrade flag.
To guarantee reproducibility across a team, commit the root module's .terraform.lock.hcl to version control. For reusable public modules, however, it is standard practice not to include the lock file.
| Element | Where defined | Primary role | Exam takeaway |
|---|---|---|---|
| required_providers | Inside the terraform block | Declares the provider source and the allowed version range | Constraints define a range — know exactly what ~> means |
| version (constraint) | The version string in required_providers | Narrows installable candidates and makes compatibility boundaries explicit | ~>1.2 allows <2.0; ~>1.2.3 allows <1.3 |
| .terraform.lock.hcl | The working directory (root) | Pins the selected exact version and per-platform checksums | Commit the lock file. Update with -upgrade or providers lock |
| terraform init / -upgrade | CLI | Initialize while respecting the lock file; -upgrade moves to the latest within constraints | A normal init never upgrades silently — updates must be explicit |
Minimal example: constraint and lock file relationship
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Allow 5.x only (constraint)
}
}
}
# First run: terraform init selects the latest 5.x and pins it in .terraform.lock.hcl
# Afterwards: terraform init honors the lock. Use terraform init -upgrade only when you want to updateConstraints are based on SemVer and use operators to define ranges. ~> is the pessimistic constraint operator: it rejects changes above the specified digit. It's effective for capping major versions while still allowing minor and patch updates.
Constraints that are too strict make upgrades painful, and constraints that are too loose invite unexpected breaking changes. Agree on a team-wide policy (e.g., lock major, allow minor) and apply it consistently.
Representative constraint patterns
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Allow the latest 5.x
}
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.80, < 4.0" # Explicit range
}
google = {
source = "hashicorp/google"
# version omitted: no constraint (without a lock file, the latest is picked)
}
github = {
source = "integrations/github"
version = "= 6.3.0" # Exact pin (a range is generally preferred)
}
}
}
# Note: if you change the constraint and the version recorded in the lock file falls outside it,
# the next init will fail and require you to run -upgrade..terraform.lock.hcl stores the exact selected version for each provider along with per-platform checksums. This makes init highly reproducible.
A normal terraform init prefers the version recorded in the lock file and never updates silently, even within the allowed range. Use terraform init -upgrade only when you actually want to update. If you run on multiple OS/architecture combinations, use terraform providers lock -platform to add checksums for the target platforms ahead of time.
Decision flow at init time (constraints, lock, and registry)
Updating the lock and adding checksums for multiple platforms
# Update to the latest within constraints and rewrite the lock
terraform init -upgrade
# Pre-add checksums for CI / multi-OS use
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=windows_amd64
# After the change, commit .terraform.lock.hclDay-to-day CI should use a normal terraform init and run plan/apply according to the lock file. Reserve -upgrade for scheduled maintenance jobs that open PRs — that's how you can safely roll in updates.
In internal-network or air-gapped environments, prefer a mirror via provider_installation in the CLI configuration file, and if needed forbid direct downloads for specific providers to maximize reproducibility.
CI example (regular pipeline and weekly upgrade)
# Regular pipeline
terraform init -input=false
terraform validate
terraform plan -input=false
# Weekly upgrade job (run on a separate branch)
terraform init -upgrade -input=false
# Confirm that only .terraform.lock.hcl was changed, then open a PR
git add .terraform.lock.hcl
git commit -m "chore: upgrade providers within constraints"
# push -> verify plan in CI
# Mirror configuration (~/.terraformrc or terraform.rc)
provider_installation {
filesystem_mirror {
path = "/opt/terraform/providers" # Sync the mirror in advance
}
direct {
exclude = ["hashicorp/aws"] # aws must come from the mirror; fail if not found
}
}init can fail because constraints and the lock file are out of sync, or because checksums are missing for a newly added platform. Read the error message carefully and decide whether you need to adjust constraints, run -upgrade, or run providers lock.
Inconsistent network or mirror configuration can also break fetches. Double-check the basics: typos in the source address, or the relevant version missing from the mirror.
Commands by situation
# Update the lock while keeping the constraints
terraform init -upgrade
# Add a new platform to the lock
terraform providers lock -platform=linux_arm64
# Example of revising constraints (bumping a major version)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0" # When migrating from 5.x to 6.x
}
}
}
# Run init -upgrade afterwardsThe exam frequently asks about the division of roles between constraints and the lock file, the meaning of ~>, when to use init vs. -upgrade, and lock-file commit policy. Memorizing the keywords helps you handle reworded questions and reduces careless misses.
Minimum snippet to memorize
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
# init: honor the lock / init -upgrade: update within constraints
# Commit the lock file to guarantee reproducibilityAssociate
問題 1
A team wants to stay on AWS provider major version 5 and avoid unexpected updates in day-to-day CI, while still rolling in the latest 5.x release once a month in a planned way. Which operation is the most appropriate?
正解: A
Narrow the range to 5.x with a constraint and commit the lock to guarantee reproducibility. A normal init honors the lock; only -upgrade rolls in the latest within constraints, opening a reviewable PR — this is optimal. B requires manual updates and is inefficient, C is unreproducible and unstable, and D constrains the CLI version, not the providers.
Should I commit .terraform.lock.hcl?
Yes. For root modules, committing it is recommended to guarantee reproducibility. For reusable modules consumed by others (such as public modules), the lock file is typically not included because the consumer's root will pin the versions instead.
How do I perform a major-version upgrade?
Change the required_providers constraint to allow the new major version (for example ~> 6.0), then run terraform init -upgrade to refresh the lock file. Review each provider's release notes and inspect the plan diff carefully before applying.
What should I watch out for when running CI on multiple OS/CPU combinations?
Use terraform providers lock -platform to add checksums for the target platforms to the lock file ahead of time, then commit .terraform.lock.hcl. In environments without direct registry access, configure provider_installation in the CLI configuration file so that a mirror is preferred.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...