TerraformVI · Providers and the Provider EcosystemProduction Terraform
The Provider Plugin Model
What you'll learn
- Describe the boundary between Terraform Core and a provider plugin
- Explain why providers run as separate executables and what protocol they speak
- Locate the provider cache directory on Linux and macOS hosts
- Configure a shared plugin cache to reduce downloads and improve integrity
- Recognise the production failure modes of a stale or mismatched plugin cache
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
A Terraform provider is not a library linked into the CLI. It is a separate executable that Terraform Core launches when the configuration is initialised. The boundary is deliberate: it lets HashiCorp, AWS, Azure, GCP, and thousands of third parties ship providers at their own cadence without rebuilding Core, and it lets you pin the version you trust. The lesson teaches what that boundary means in production: where the binaries live, how Core finds them, and what to do when the cache goes wrong.
The boundary: Core and provider as two processes
When you run terraform plan, two processes exist for the
duration of the command:
+--------------------+
| terraform CLI |
| (Core binary) |
+---------+----------+
|
| Plugin Protocol over stdin/stdout
| (gRPC, JSON-encoded)
|
+---------v----------+
| provider "aws" |
| (separate binary) |
+--------------------+
Core reads the configuration, builds the resource graph, and delegates every CRUD call against a resource to the provider process. The provider does the API work against AWS, GCP, GitHub, or whatever service it represents.
This model is from Terraform 0.1. The plugin protocol itself has evolved. Terraform 1.x uses a Go-plugin-based RPC. OpenTofu 1.7 diverged in late 2024 to its own protocol iteration but the operational model — Core launches the provider, the provider talks back over a structured protocol — is unchanged.
Where the binaries live
terraform init downloads provider binaries into a working
directory under .terraform/providers/. The path encodes the
source, version, and operating system:
.terraform/
└── providers/
└── registry.terraform.io/
└── hashicorp/
└── aws/
└── 5.80.0/
└── linux_amd64/
└── terraform-provider-aws_v5.80.0_x5
Three pieces of context are encoded in the path:
- Hostname —
registry.terraform.iois the public registry. A self-hosted private registry or filesystem mirror changes this segment. The next lesson covers sources. - Namespace and type —
hashicorp/aws. The source address is the unique key. - Version and platform —
5.80.0/linux_amd64. The lock file records the exact version and the hashes for one or more platforms.
You can list what is on disk:
# READ-ONLY: list the provider binaries Terraform has cached.
find .terraform/providers -type f -name 'terraform-provider-*'
.terraform/providers/registry.terraform.io/hashicorp/aws/5.80.0/linux_amd64/terraform-provider-aws_v5.80.0_x5
.terraform/providers/registry.terraform.io/hashicorp/local/2.5.1/linux_amd64/terraform-provider-local_v2.5.1_x5
If a binary is missing or unreadable, Terraform prints an error
on the next init or plan. That error is the only signal the
team will get that the cache is corrupt.
The plugin cache
The .terraform/providers/ tree is per working directory. A
fresh git clone followed by terraform init re-downloads every
provider, which is wasteful on CI runners that build many
working trees. The solution is a shared plugin cache, configured
once on the host:
# CONFIGURATION: enable a shared, host-wide plugin cache.
mkdir -p /var/cache/terraform-plugin
chmod 0755 /var/cache/terraform-plugin
cat > ~/.terraformrc <<'EOF'
plugin_cache_dir = "/var/cache/terraform-plugin"
EOF
Or, for a CI runner:
# CONFIGURATION: machine-wide CLI config.
sudo tee /etc/terraformrc <<'EOF'
plugin_cache_dir = "/opt/terraform-plugin-cache"
EOF
With plugin_cache_dir set, Terraform still writes provider
metadata into .terraform/providers/ under each working
directory, but it skips re-downloading the binary if the same
version is already present in the shared cache.
The lock file and platform hashes
The .terraform.lock.hcl records the version and the hashes
Terraform verified when it first downloaded the binary. The
hashes are platform-specific:
provider "registry.terraform.io/hashicorp/aws" {
version = "5.80.0"
hashes = [
"h1:abcd1234...", # linux_amd64
"h1:efgh5678...", # darwin_amd64
"h1:ijkl9012...", # darwin_arm64
]
}
A team with mixed Linux and macOS workstations ends up with
multiple hashes per provider. The terraform providers lock -platform=<list> command records additional platforms
explicitly; terraform init records the platform it was run
on. Both should be committed to Git.
Discovery: how Core finds the binary
When terraform plan runs, Core performs the following:
- Read the lock file. Determine the resolved version.
- Read
required_providers. Determine the source and constraint. - Locate the binary. Look first in
.terraform/providers/, then inplugin_cache_dir, then download from the source. - Hash the binary. If the computed hash is not in the lock file, Terraform re-downloads and updates the lock file. If it is, it proceeds.
- Launch the provider. Core spawns the binary as a child process and communicates over the plugin protocol.
The discovery path is short. It is also the source of several production failure modes.
Failure modes in the plugin model
Five failure modes recur in production:
1. Stale plugin cache after a provider upgrade. A developer
upgrades the constraint in required_providers, but init -upgrade fails because the cache directory is read-only or the
hash in the cache does not match. The plan errors out with
“provider hash mismatch”. The fix is to clear the cache for
that provider or run init -upgrade with a writable cache.
2. Missing platform hash. A developer on macOS Apple
Silicon commits a lock file that only contains linux_amd64
hashes. CI on Linux runs terraform init and Terraform
downloads a new binary and updates the lock file. The diff is
the new hash. The fix is to commit hashes for every platform
the team uses: terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 -platform=darwin_amd64.
3. Plugin binary deleted mid-run. A script cleans up
~/.terraform.d/ or /tmp/.terraform/ on a shared runner.
The next plan errors with “plugin not found”. The fix is to
configure a persistent plugin_cache_dir outside the ephemeral
filesystem.
4. Provider signature mismatch on a private registry. A
self-hosted registry is upgraded and signs binaries with a new
GPG key not present in the CLI configuration. init fails with
“untrusted signature”. The fix is to update
provider_installation in the CLI config to point at the new
signing key.
5. Wrong OS arch pulled in. A macOS engineer commits a hash that Terraform never computed — perhaps from a manual edit to the lock file. CI rejects the plan. The fix is to never hand- edit the lock file. Regenerate it.
Operational guidance
For a production Terraform estate:
- Commit the lock file. It is the reproducibility contract.
Without it, two engineers running
initon the same day can resolve different provider versions. - Pin a shared plugin cache on CI runners. Set
plugin_cache_dirto a directory that survives between workflow runs. A path under/var/cacheor/opt/terraform-plugin-cacheis typical. - Pin a shared plugin cache on developer hosts. Same file. Reduces the time to first plan and reduces the surface for inconsistent hashes.
- Record all platforms the team uses. Run
terraform providers lock -platform=<list>once on each supported OS and commit the union. - Upgrade deliberately. An
init -upgradefollowed by aplanis the standard pattern; the plan is the audit.
What comes next
The next lesson is on provider sources — the registry, the private registry, and the local filesystem mirror that determines where the binaries are downloaded from.
Verification
Knowledge check · 6 questions
Q1. How does Terraform Core invoke a provider plugin?
Q2. Which directory contains the provider binaries downloaded by `terraform init`?
Q3. The shared plugin cache (`plugin_cache_dir`) replaces the per-working-directory `.terraform/providers/` tree.
Q4. A macOS Apple Silicon engineer commits a lock file. CI on Linux rejects it because the hash is missing. What is the fix?
Q5. Which of the following are production failure modes of the plugin cache? (Select all that apply.)
Q6. A CI runner cleans `/tmp/.terraform` between workflow runs. Apply jobs fail with `plugin not found`. What is the right mitigation?
Passing score: 75%. Answers are checked in this browser.