Terraform Module Source Validator

Check every module block for the pinning mistakes that let module code change without a change to your configuration. The commonest is version on a git source, where it does not apply.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Validate. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

version on a git source

version is registry-only, and deleting it to fix the error leaves the module completely unpinned

module "vpc" {
  source  = "git::https://github.com/org/tf-vpc.git"
  version = "1.2.3"
}

A git source with no ref

Terraform clones the default branch, so the module changes whenever somebody merges

module "vpc" {
  source = "git::https://github.com/org/tf-vpc.git"
}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Writing version alongside a git source

    version is registry-only. Terraform errors, and the reflex fix is deleting the version line, which leaves the module tracking a branch with no pin at all.

    Instead:Pin in the source: append ?ref=v1.2.3 for a tag, or a full commit sha for something that cannot move.

  2. Assuming the lock file pins modules

    .terraform.lock.hcl records providers only. Two inits weeks apart can fetch different module code with nothing in version control to show it.

    Instead:Add a version constraint on every registry module and a ref on every git module. There is no lock file to fall back on.

  3. Interpolating the source address

    source is resolved before variables exist, so it must be a literal. The error Terraform gives names variables rather than the source line, which sends people looking in the wrong place.

    Instead:Use separate module blocks with count or for_each rather than one dynamic source.

The module lock file does not exist, so pinning is entirely on you

Terraform locks providers. It does not lock modules. Whatever your source address resolves to at init time is what you get, and that is the whole problem.

version applies to registry sources only

This is the mistake that looks most like a fix. The version argument is meaningful only when source is a registry address; on a git or http source Terraform rejects it. The dangerous part is the correction: people delete the version line, the configuration is then valid, and the module is completely unpinned. For git the pin belongs in the source address as ?ref=

A branch ref is not a pin

?ref=main is in the ref position and looks like a version, and it gives you whatever was merged most recently. A tag is better and can still be moved by whoever owns the repository. A full commit sha is the only genuinely immutable option, and it is the right choice when reproducibility matters more than readability.

The lock file covers providers, not modules

This is the detail that makes unpinned modules bite. .terraform.lock.hcl records provider versions and hashes, so people reasonably assume modules are covered too. They are not. Two inits weeks apart can fetch different module code with no change to your configuration and nothing in version control to show it.

source is read before anything is evaluated

It has to be a literal string. Variables, locals and data sources do not exist at the point Terraform resolves module sources, so an interpolated source cannot work and the error names variables rather than the source line. If you need different modules per environment, use separate module blocks with count or for_each on each rather than one dynamic source.

A local path must begin ./ or ../

Anything else is read as a registry address. So source = "modules/vpc" is not the local directory you meant, it is an attempt to fetch a registry module with a malformed address, and the error is about the registry rather than the path.

What this cannot see

Whether the module exists, whether the version constraint resolves to anything, or what the module does. It reads the source addresses in the text you paste, in your browser, and contacts no registry. Run terraform init to confirm the addresses actually resolve.

More terraform tools

Terraform Validator & Formatter Does it parse, and is it formatted? Terraform dynamic Block Generator It is ingress.value, not each.value Terraform check and precondition Generator A failed check does not stop the apply Terraform variables.tf Generator From the var. references you already wrote Terraform Unused Declaration Linter Dead variables, locals and data sources Terraform Atlantis YAML Generator when_modified misses your modules Terraform Function Tester and HCL Console terraform console, in a tab Terraform for Expression Tester Where do the keys collide? Terraform Interpolation Tester null becomes an empty string Terraform templatefile Tester Render it before you apply it Terraform Resource Count Calculator How many objects, not how many blocks Terraform count to for_each Converter With the moved blocks it needs Terraform moved Block Generator Rename without destroying Terraform state mv Command Builder No plan, no undo Terraform target Flag Builder And why to stop using it Terraform outputs.tf Generator Outputs are not sensitive by default Terraform Remote State Data Source Generator It grants read of the whole state Terraform Provider Block Generator docker is not hashicorp/docker Terraform .gitignore Generator and Checker .terraform* eats the lock file Terraform to OpenTofu Migration Checker The state is the part to watch Terraform tfvars to JSON Converter The JSON file wins Terraform Version Constraint Parser What ~> actually allows Terraform Resource Address Parser Quote it before the shell eats it Terraform cidrsubnet Calculator newbits is added, not the target Terraform Import Block Generator The id is not the name Terraform Lock File Viewer Why CI fails and your laptop does not Terraform Variables Checker A missing value hangs CI Terraform tfvars to Env Vars Converter Lists and maps have to be JSON Terraform Deprecated Syntax Checker What a current Terraform rejects Terraform State File Viewer Read it without uploading it Terraform Plan JSON Viewer It is not redacted Terraform HCL to JSON Converter And what the conversion loses Terraform Backend Config Decoder Is state actually locked? Terraform Naming Convention Validator A hyphen makes a resource unreferenceable

Elsewhere on the site