Terraform

Terraform Provider Version Constraints and Lock File Operations

2026-04-19
NicheeLab Editorial Team

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.

Big Picture: required_providers and the Lock File

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.

  • Constraints = allowed range; lock file = the actual version and hashes used
  • init honors the lock file; updates require terraform init -upgrade
  • Commit the lock file in root modules (typically not committed for reusable modules)
ElementWhere definedPrimary roleExam takeaway
required_providersInside the terraform blockDeclares the provider source and the allowed version rangeConstraints define a range — know exactly what ~> means
version (constraint)The version string in required_providersNarrows installable candidates and makes compatibility boundaries explicit~>1.2 allows <2.0; ~>1.2.3 allows <1.3
.terraform.lock.hclThe working directory (root)Pins the selected exact version and per-platform checksumsCommit the lock file. Update with -upgrade or providers lock
terraform init / -upgradeCLIInitialize while respecting the lock file; -upgrade moves to the latest within constraintsA 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 update

Writing Version Constraints and Their Pitfalls

Constraints 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.

  • ~> 1.2 means >=1.2.0 and <2.0.0
  • ~> 1.2.3 means >=1.2.3 and <1.3.0
  • >=, <=, >, <, =, != can be combined with commas (AND chaining)
  • No constraint means an unbounded range; without a lock file, the first run picks the latest version

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.

How the Dependency Lock File (.terraform.lock.hcl) Works

.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.

  • Recorded content: provider source, selected version, and per-platform SHA256 checksums
  • Triggers for updating the lock: terraform init -upgrade, or terraform providers lock
  • Generate the lock file per root and commit it to the repository to share

Decision flow at init time (constraints, lock, and registry)

required_providersRange specification.terraform.lock.hclExact version and checksumsProvider Registry / MirrorDownloadterraform init: 1) read constraints 2) honor the lock 3) fetch if needed 4) update the lock

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.hcl

Reproducibility and Planned Upgrades in Teams and CI

Day-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.

  • Daily: init (without -upgrade) -> validate/plan/apply
  • Scheduled: init -upgrade -> open a PR that touches only .terraform.lock.hcl -> verify behavior, then merge
  • When using a mirror, configure the order and include/exclude lists in provider_installation correctly

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
  }
}

Common Errors and How to Handle Them

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.

  • locked version X does not match...: the constraint no longer allows the version in the lock. Relax the constraint, or run init -upgrade
  • checksum list for platform is empty: a new OS/CPU was added. Append checksums with providers lock -platform and commit
  • Failed to query available provider packages: check the source address, network, and mirror configuration
  • Inconsistencies you simply cannot resolve: as a last resort, regenerate the lock file with init -upgrade and review the diff

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 afterwards

Associate Exam Key Points Checklist

The 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.

  • required_providers defines a range; .terraform.lock.hcl pins the exact version
  • A normal terraform init honors the lock; updates require terraform init -upgrade
  • ~>1.2 means <2.0; ~>1.2.3 means <1.3
  • Commit the lock file in root modules; typically don't include it in reusable modules
  • Use providers lock -platform to record checksums for multiple OS/CPU combinations in advance

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 reproducibility

Check Your Understanding

Associate

問題 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?

  1. Set aws version to "~> 5.0" in required_providers and commit .terraform.lock.hcl. Use a normal terraform init day-to-day, and run terraform init -upgrade in a monthly job that opens a PR.
  2. Pin to "= 5.0.0" in required_providers and do not commit .terraform.lock.hcl. Manually edit the version number when needed.
  3. Leave the version constraint empty and don't commit .terraform.lock.hcl. Always use the latest and roll back if problems arise.
  4. Only set Terraform's required_version and don't manage provider constraints at all.

正解: 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.

Frequently Asked Questions

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.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

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.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.