Terraform Provider Lock Files: Reproducible Installs and Trustworthy Upgrades
Use .terraform.lock.hcl to pin provider selections, verify checksums, review upgrades, and keep plans consistent across CI and developer machines.
Terraform configuration declares which provider versions are acceptable, but a version constraint is not the same thing as a selected version. A requirement such as ~> 5.0 permits a range of releases. On a fresh initialization Terraform must choose one compatible release; without a recorded choice, another machine could make a different selection after the registry publishes a newer version. The dependency lock file, .terraform.lock.hcl, records the provider decisions Terraform made so later initializations can reproduce them and verify the downloaded package.
That file is easy to overlook because terraform init manages it automatically. It is nevertheless a reviewed part of the infrastructure change. A provider upgrade can change planning, resource schemas, defaults, validation, API behavior, and state refresh results. The lock file gives reviewers an exact version and integrity data to inspect instead of asking them to infer what “latest compatible” meant on the author’s laptop.
What the lock file does and does not lock
The lock file belongs to the root configuration directory, alongside the root module’s Terraform files. It records selected provider versions and checksums. Terraform consults it during provider installation and normally reuses those selections when they still satisfy all version constraints in the configuration. A dependency change can therefore produce a lock-file diff even if no resource block changed.
The file does not lock Terraform CLI itself, remote module versions, cloud API responses, or the infrastructure state. Those require separate controls. In particular, Terraform currently does not remember remote module version selections in this file. Pin a registry module with its own version constraint when reproducibility requires an exact release, and record the Terraform CLI version through the project’s tool-version policy or CI image. A committed provider lock file is one layer of a reproducible run, not a complete software bill of materials or a guarantee that every external input is fixed.
Provider requirements are also different from provider configurations. A requirement names a provider source address, local name, and version constraint. A provider configuration supplies runtime settings such as a region or authentication context. The lock file records the selected release for a provider source, not separate versions for aliases such as aws.west and aws.east. All aliases of one provider source use the same installed provider version.
First initialization and normal reuse
When no selection exists for a required provider, terraform init chooses the newest version that satisfies all constraints from the root and child modules, then writes the selection and acceptable package hashes. The generated entry is not a declaration of what versions are allowed; that remains the job of required_providers. The lock file explains which compatible version this configuration currently uses.
On later initializations, Terraform selects the recorded version as long as it still satisfies the constraints. If a module tightens its minimum above the locked release, the old selection can no longer be used and initialization must find a new compatible one. If the lock file disappears, Terraform treats the working directory as having no recorded selection and can choose a different compatible version. This is why the file should be committed and why CI should not generate and silently discard it on every run.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.40, < 6.0"
}
}
}
This requirement expresses a compatibility range, not a precise installation. The lock file supplies the current exact selection within that range. Reusable child modules should ordinarily state a suitable minimum they have tested rather than impose a narrow exact version on every caller. The root configuration’s lock file then represents the single provider version Terraform selected after combining all module constraints.
Checksums and what verification means
Each selected package is checked against hashes in the lock file. If an installed archive does not match any recorded checksum for that provider version, Terraform fails rather than continuing with an unexpected binary. For the public origin registry, Terraform can use signed checksum information from the provider publisher and may record hashes for packages on more than one platform. During initial selection, read the CLI output that identifies the signing key and verify that the publisher identity is expected for your organization’s trust policy.
The first selection is still a trust-on-first-use decision. A checksum proves that future downloads match the accepted digest; it does not independently prove that the initially accepted provider was safe, that the source address was the intended publisher, or that the provider code has no defects. Review provider source addresses, ownership, release notes, signatures, and organizational approval before accepting a new provider or a new major release. Treat a checksum mismatch as an integrity incident to investigate, not as a prompt to delete the lock entry until installation succeeds.
An alternative provider mirror may not be able to supply the origin registry’s signed checksum set. In that case the first initialization may record only checksums for the platform used on that machine. A team using multiple operating systems should deliberately populate hashes for each supported target with terraform providers lock -platform=..., using the organization’s approved provider source and review process. Otherwise a colleague may hit a checksum failure on a different operating system even though the package is legitimate.
Review an intentional provider upgrade
Run terraform init -upgrade in a controlled branch when you intend to reconsider locked selections. Terraform ignores current selections for this operation, chooses the newest versions allowed by the constraints, and updates the lock file. This does not mean that every provider is upgraded to its newest published release: constraints, platform support, and other modules’ requirements still apply.
Review the resulting diff before applying anything. For each changed provider, verify the source address, old and new versions, release notes, deprecations, changed schemas, and the checksums. Then run formatting, validation, module tests, and a fresh plan in a representative non-production environment. A plan that changes dozens of unrelated resources after an upgrade is a signal to understand the provider behavior before approval, not a reason to apply quickly.
The terraform init -upgrade flag is not a per-provider selector: Terraform reconsiders all provider selections that the configuration allows to change. If a change request is meant to upgrade one provider, narrow the relevant requirement deliberately where appropriate, run initialization, and still inspect every lock-file diff because constraints can interact across modules. Do not hand-edit the version and hash values to force a desired result. Terraform can regenerate the file, but manual editing bypasses the selection logic and can remove cross-platform hashes. After an approved upgrade, commit the lock-file change with the configuration and test evidence that justified it.
CI policy and cross-platform teams
A dependable pipeline treats the lock file as read-only during ordinary plans. A common workflow initializes against the committed selection, checks formatting and syntax, validates the configuration, and then creates a saved plan under the approved identity. Use Terraform’s read-only lock-file mode where supported by the CLI version in your toolchain so an unexpected dependency change causes the run to fail instead of producing an unreviewed lock diff. Upgrade jobs can use a separate explicit path that proposes, tests, and reviews lock-file changes.
terraform fmt -check -recursive
terraform init -lockfile=readonly
terraform validate
terraform plan -out=tfplan
Keep the CLI version consistent between local development and CI. The dependency lock file cannot compensate for differences in Terraform language support or CLI behavior. For multi-platform repositories, add all supported operating-system and architecture hashes in a controlled initialization step, then check that the committed file contains them. Avoid running separate unconstrained upgrades from multiple machines and merging the resulting lock diffs without reconciling which provider versions and hashes are intended.
Do not commit the provider cache, downloaded modules, state snapshots, or saved plan files as a substitute for the lock file. Caches are performance aids and can be repopulated. State and plans may contain sensitive values. The lock file is appropriate for version control because it is dependency metadata and integrity information, but it still deserves review and change ownership.
Diagnose lock-file problems without weakening the boundary
When initialization reports a version conflict, inspect the constraints contributed by every module before changing the lock file. A child module may require a newer minimum or an incompatible upper bound. Resolve the actual constraint conflict, choose an upgrade policy, and then let Terraform update the selection. When the error is a checksum mismatch, preserve the logs and identify the exact provider source, version, mirror, platform, and package digest. Check whether the mirror is serving the expected package and whether the lock file includes a hash for that platform.
If a lock entry is missing on a new workstation, first confirm that the correct root directory and branch are checked out. Running Terraform from a nested module directory can use a different working configuration and a different lock context. If the lock file changed unexpectedly, inspect the diff and the exact initialization command; common causes include a deleted file, an unreviewed -upgrade run, a changed provider constraint, or a new child module requirement.
The lock file is a small artifact with an outsized operational role. Keep it under version control, separate accepted selections from upgrade intent, validate hashes across every supported platform, and make provider updates a reviewed change. That gives a team repeatable installations without confusing reproducibility with security approval or pretending that provider dependencies are the only changing input in an infrastructure run.
Related:
- Terraform State in Production: Backends, Locking, Drift, Import, and Recovery
- Infrastructure as Code: Terraform State, Drift, and Idempotency
Sources: