Versions, providers and secrets
🎯You'll be able to
Pin Terraform and provider versions, commit the lock file, and generate secrets in Terraform instead of writing them in files.
🏠The everyday picture
A pharmacist dispenses "paracetamol 500 mg, batch 2611" rather than "some painkiller". The exact batch means the next refill behaves the same, and a recall reaches exactly the right strips.
📘What it really is
-
Pin versions in a
terraformblock:hcl terraform { required_version = ">= 1.9" required_providers { docker = { source = "kreuzwerker/docker", version = "~> 4.6" } random = { source = "hashicorp/random", version = "~> 3.9" } } }(In your files, write each provider over several lines;terraform fmtlays them out.)~> 4.6allows 4.6 and later 4.x versions but not 5.0, which may change behaviour.~> 4.6.0would allow only 4.6.x. -
The lock file,
.terraform.lock.hcl, records the exact provider versions and their checksums chosen byterraform init. Commit it, so everyone and every pipeline uses the same build.terraform init -upgrademoves to newer versions within your constraints, on purpose, in a reviewed change. -
Generate secrets: the
randomprovider'srandom_passwordmakes a strong password and keeps it in state:hcl resource "random_password" "db" { length = 24 special = false }Use it asrandom_password.db.resultin both the database and the API's connection string. Nobody types it, it isn't in Git, and each environment gets its own. -
Sensitive values: mark variables and outputs
sensitive = trueso Terraform doesn't print them in plans and logs. They are still in state (Lesson 3). -
Secrets from outside: in the cloud, a secrets manager often holds the secret, and Terraform either creates it there or passes only its name to the app, which reads it at start-up (CD6).
📝A worked example
Input: replace POSTGRES_PASSWORD=routerani123 in the starter's main.tf with random_password.db.result, add the random provider with a
version, run terraform init and terraform apply.
Expected output: init records hashicorp/random in .terraform.lock.hcl; the plan shows random_password.db to create and the password
as (sensitive value). git grep routerani123 finds nothing in infra/. Project 1 test 2 now passes its password and version checks.
🌍Where it's used
- Every serious Terraform repository pins providers and commits the lock file, so a provider release can't change production by surprise.
- Database and service credentials generated per environment.
- Security scanners (Checkov, tfsec, Trivy) flag literal passwords and unpinned providers in pull requests.
📝Case scenario
A logistics company's Terraform had no provider version. One Monday, a pipeline picked up a new major version of the cloud provider whose defaults had changed, and the plan wanted to replace 30 load balancer rules. A careful reviewer stopped it. They pinned the provider, committed the lock file, and upgraded later in a planned change with the release notes open.
🖱️Try it
Run terraform version and terraform providers in infra/, then open .terraform.lock.hcl. Success: you can name the exact provider
versions in use and the constraint each one satisfies.
⚠️Common mistakes
- No version constraints. Sign: plans change after a provider release with no code change. Fix:
~>constraints and a committed lock file. - Literal passwords in
.tffiles. Sign:git grep -i passwordshows real values. Fix:random_passwordor a secrets manager, and change the old password. - Assuming
sensitiveencrypts. Sign: state stored carelessly because "it's marked sensitive". Fix: protect state itself.
🛠️For your project
Project 1 test 2 checks that every provider has a version, no image uses latest, and no database password is written in a .tf file.
✅Quick check
What does ~> 4.6 allow?
Show the answer
Version 4.6 and later 4.x releases, but not 5.0.
Why commit .terraform.lock.hcl?
Show the answer
So everyone and every pipeline uses the exact same provider builds.
