# Terraform modules: pin every external module version
## Why
```
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
}
```
This resolves to the newest VPC module at every fresh init. Registry modules release breaking changes in majors just like providers do. Unpinned modules are the same time bomb as unpinned providers, one layer up.
## How
Pin with an exact version or a bounded range:
```
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}
```
## Rules for agents
1. Every module with a registry source gets a `version`. Local path modules do not need one (there is nothing to resolve).
2. Prefer `~>` ranges over exact pins for modules you maintain; prefer exact pins in frozen environments where reproducibility beats freshness.
3. Upgrading a module version is a task: read the module's changelog, bump, `terraform init -upgrade`, full plan, review every proposed change. Modules can rename resources between majors, which shows up as destroy/create pairs.
4. Private registry modules follow the same rule. "Internal so it is safe" is how internal breakage ships.
5. When a plan shows unexpected destroy/create pairs after an init, check module and provider version changes first, config changes second.