Providers are the plugins that let Terraform manage real things. Find them on the Registry, configure them, and pin their versions so your builds stay reproducible.
Why: Terraform itself manages nothing — every resource type comes from a provider, a plugin that translates HCL into API calls. AWS, Azure, GCP, Kubernetes, Docker, GitHub, and hundreds more each have one. You declare which providers you need in a required_providers block; init downloads them.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker" # namespace/name on the Registry
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
}
}
}Why: the Registry (registry.terraform.io) is where providers and modules live. Each provider page lists its source address, every resource and data source it offers, and copy-paste examples. The source is always namespace/name — hashicorp/aws, kreuzwerker/docker — which is exactly what goes in required_providers. See https://registry.terraform.io.
registry.terraform.io
└── hashicorp/aws ← provider page
├── source = "hashicorp/aws" (copy this into required_providers)
├── Resources: aws_instance, aws_s3_bucket, ...
└── Data Sources: aws_ami, aws_vpc, ...Why: a provider block configures HOW the provider connects — region, host, credentials. The local and random providers need no configuration at all; the docker provider just needs the host (it defaults to your local Docker). Note: never hardcode secrets here — pass them via variables or environment variables, covered in the Variables lesson.
provider "docker" {
# talks to your local Docker daemon by default
}
# A configured cloud provider looks like this (no secrets in the file!):
provider "aws" {
region = "eu-west-1"
}Why: without version constraints, a fresh init could pull a newer provider that breaks your config. The ~> operator (pessimistic constraint) allows patch/minor updates but not the next major: "~> 3.0" means >= 3.0 and < 4.0. Commit the generated .terraform.lock.hcl so everyone — and CI — uses the exact same provider builds.
terraform {
required_version = ">= 1.6" # the Terraform CLI itself
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0" # >= 3.0, < 4.0
}
}
}When you widen a version constraint or want the latest allowed builds, init -upgrade re-resolves providers and rewrites the lock file. Run it deliberately and commit the updated lock — never let versions drift silently between machines.
Re-resolve providers and update .terraform.lock.hcl
terraform init -upgrade