How it works
The explanation, with examples you can run.
Requiring and configuring
terraform { required_version = ">= 1.5" required_providers { aws = { source = "hashicorp/aws", version = "~> 6.0" } random = { source = "hashicorp/random", version = "~> 3.6" } }} provider "aws" { region = "eu-west-1"} provider "aws" { alias = "us" region = "us-east-1"}source is namespace/type on the registry (registry.terraform.io by default). The provider block holds settings such as the Region; credentials should come from the environment (a role, SSO session or CI identity), never from the file. A second configuration gets an alias, and resources choose it with provider = aws.us.
The lock file
terraform init picks the newest version matching the constraints and writes .terraform.lock.hcl:
# This file is maintained automatically by "terraform init".# Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/local" { version = "2.9.1" constraints = "~> 2.5" hashes = [ "h1:OZGJN0LSSat5QIxZPxDXtVr4XpHb2oG7cVKJhSqBFIE=", "zh:25606c7a5e308144fb627f6e31611bb52ff72bb9ae2d27af39673ab1a6b3c1bf",Commit it. Later runs use exactly this version and verify the checksums, so a laptop, CI and a colleague all run the same provider. Upgrading is explicit: terraform init -upgrade, then review the plan. If CI runs on Linux and people on macOS, record hashes for both with terraform providers lock -platform=linux_amd64 -platform=darwin_arm64.
Resources and data sources
data "aws_vpc" "shared" { tags = { Name = "shared-network" }} resource "aws_security_group" "api" { name = "api" vpc_id = data.aws_vpc.shared.id}The VPC is looked up on every plan and never modified; the security group is created, tracked in state, updated and, if you remove the block, destroyed. Every resource has an address (aws_security_group.api) that Terraform uses in plans and state, and each argument's behaviour (in-place update or forced replacement) is defined by the provider's schema, which is why the provider documentation is the reference you will read most.
Things that go wrong
- Unpinned providers: a major version changes defaults or removes arguments, and the next plan wants to replace things.
- Provider configuration inside reusable modules: modules should declare
required_providersbut receive provider configurations from the caller, so they can be used with any Region or account. - Data sources that find nothing or several results fail the plan. That is usually a feature: better than guessing.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.