Skip to main content

Exclude .terraform.lock.hcl from Version Control

Date: 2026-05-10 [Format=YYYY-MM-DD]

Status

  • Draft
  • Proposed
  • Accepted
  • Deprecated
  • Superseded by 0002

Decision

Exclude the Terraform dependency lock file (.terraform.lock.hcl) from version control across every Terraform component and blueprint in this repository, and rely on explicit provider version constraints plus a fresh terraform init in CI to guarantee reproducible builds.

Context

The OSSF Scorecard Silver criterion build_repeatable requires that the project's build can be repeated by anyone, from a clean checkout, without unstated environmental assumptions. Issue microsoft/edge-ai#166 (build_repeatable — Terraform half) flagged that the repository excludes .terraform.lock.hcl via .gitignore without a recorded rationale and without a documented alternative for guaranteeing reproducibility, which is the artifact that OSSF Silver expects to see.

This repository spans many independent Terraform components and blueprints (src/000-cloud/..., src/100-edge/..., blueprints/...) and is consumed from multiple operating systems and CI runners (Linux x86_64, macOS arm64/x86_64, Windows x86_64, Linux arm64 dev containers). A single committed lock file records provider package hashes only for the platforms that were initialized when the file was generated. When a contributor on a different platform runs terraform init, Terraform either rejects the run with Failed to query available provider packages / checksums previously recorded in the dependency lock file errors, or silently appends new hashes that then drift between branches — both of which undermine repeatability rather than improve it.

Reproducibility for this repository is instead enforced through:

  • Explicit provider version constraints in every component's versions.tf (or providers.tf) with pinned required_providers { source, version } blocks, so the resolved provider set is deterministic from the manifest alone.
  • CI pipelines (azure-pipelines.yml, GitHub Actions) and the local validation scripts under scripts/ always run terraform init from a clean working directory, so the provider plugins are re-fetched and re-verified against the registry on every build.
  • The npm run tf-validate and npm run tflint-fix-all workflows referenced in .github/copilot-instructions.md exercise this clean-init path locally, matching CI behavior.

Recording this decision closes the documentation gap that build_repeatable calls out without changing the existing .gitignore behavior or any working contributor flow.

Decision drivers

  • OSSF Scorecard Silver build_repeatable requires a documented, repeatable build path; the existing .gitignore line for .terraform.lock.hcl had no accompanying rationale.
  • Lock files contain platform-specific provider hashes; committing one generated on a single OS/arch breaks terraform init for contributors and CI runners on other platforms.
  • The repository contains many independent Terraform working directories; managing a separate lock file per component multiplies the cross-platform hash maintenance burden.
  • Provider versions are already pinned in each component's versions.tf, giving deterministic resolution without the lock file.
  • CI consistently performs a fresh terraform init per run, so cached provider state from a committed lock file would not change CI behavior.

Considered options

  1. Exclude .terraform.lock.hcl and rely on versions.tf pins (selected)

    • Good, avoids cross-platform hash conflicts during terraform init.
    • Good, keeps the source of truth for provider versions in human-readable versions.tf files that already live next to each component.
    • Good, matches what CI already does (clean init every run).
    • Bad, does not record the exact provider package hashes that were used at a given commit; reproducibility depends on the registry continuing to serve the pinned versions.
  2. Commit a single .terraform.lock.hcl per component

    • Good, captures exact provider hashes per component.
    • Bad, generated on one platform, breaks terraform init on others unless every contributor runs terraform providers lock -platform=... for the full matrix on every provider bump.
    • Bad, multiplies maintenance across dozens of components and blueprints.
    • Bad, increases churn in PRs unrelated to provider changes.
  3. Commit a multi-platform .terraform.lock.hcl per component

    • Good, addresses the cross-platform hash problem from option 2.
    • Bad, requires every contributor (and CI) to run terraform providers lock -platform=linux_amd64 -platform=linux_arm64 -platform=darwin_amd64 -platform=darwin_arm64 -platform=windows_amd64 after any provider change in any component.
    • Bad, the maintenance cost scales with the number of components and the frequency of provider updates, with no observed benefit over option 1 given that CI re-initializes every run.
  4. Pin providers via a private mirror or dev_overrides

    • Good, gives full control over which provider artifacts CI consumes.
    • Bad, requires standing up and maintaining mirror infrastructure that the project does not currently operate.
    • Bad, out of scope for the build_repeatable gap this ADR addresses.

Decision Conclusion

.terraform.lock.hcl remains excluded from version control via .gitignore. Reproducibility is provided by pinned required_providers entries in each component's versions.tf and by CI pipelines that always run a clean terraform init. This ADR is the recorded rationale required by OSSF Silver build_repeatable.

Consequences

  • The repository satisfies the documentation expectation behind build_repeatable for the Terraform portions of the codebase.
  • Contributors do not need to regenerate or merge .terraform.lock.hcl files when working across operating systems or architectures.
  • Bumping a provider version is a single edit to the relevant versions.tf followed by terraform init and validation; there is no lock-file step.
  • If a pinned provider version is later yanked from the Terraform Registry, builds for that component will break until the pin is updated. This is an accepted trade-off; the registry's published immutability policy makes this rare in practice.
  • Any future move to a stricter supply-chain posture (for example, OSSF Gold or a private provider mirror) would supersede this ADR with an option that records exact artifact hashes.

References