Terraform Providers

Terraform's core knows nothing about AWS, Kubernetes, GitHub, or Datadog. Everything it manages comes from providers: plugins that translate Terraform resources into API calls for a specific platform. There are thousands of them, from the major clouds to DNS hosts, SaaS tools, and databases, which is a large part of why Terraform works as a universal infrastructure-as-code tool.

Handling providers well means pinning versions so upgrades are deliberate, authenticating without long-lived keys, configuring multiple regions or accounts with aliases, and using data sources to read existing infrastructure instead of hard-coding IDs.

TL;DR

Quick Example

Core Concepts

Provider Sources and the Registry

A provider source address has three parts: [hostname/]namespace/type. hashicorp/aws is shorthand for registry.terraform.io/hashicorp/aws. Providers come in tiers: official (maintained by HashiCorp), partner (maintained by the vendor, such as Cloudflare or Datadog), and community. Check maintenance activity before depending on a community provider.

Version Constraints

Root modules usually use ~> MAJOR.MINOR. Shared modules should declare the widest range they actually support (>= 5.0, < 7.0) so they don't conflict with callers.

The Dependency Lock File

.terraform.lock.hcl records the exact provider versions selected and their checksums for each platform. Commit it so every machine and CI run uses identical provider builds. terraform init -upgrade picks newer versions within your constraints and updates the lock file, which you then review in a pull request. For teams on mixed OSes, terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 records hashes for all platforms.

Configuration and Authentication

Provider blocks set region, endpoints, and credentials. Credentials should come from the environment, not code:

Never put access keys in .tf files or variables committed to Git.

Aliases

Multiple configurations of the same provider are distinguished by alias. Resources select one with provider = aws.eu; modules receive them through a providers map:

The same approach handles multiple accounts (one alias per assumed role) and multiple Kubernetes clusters.

Data Sources

A data block reads information from the provider without managing it: the latest AMI, an existing VPC by tag, a Route 53 zone, the current account ID. Data sources keep configurations portable and avoid copy-pasted IDs. They run during plan, so the resource they read must already exist, or depend on something created earlier in the same run.

Best Practices

Pin and Upgrade Deliberately

Constrain to a major version, commit the lock file, and upgrade on a schedule (Renovate and Dependabot support Terraform providers). Read the upgrade guide before crossing a major version; AWS provider majors have historically split or renamed resources.

Use default_tags and Provider-Level Defaults

Setting tags, labels, or project IDs once at the provider level ensures every resource gets them, which is essential for cloud cost allocation and ownership tracking.

Keep Providers Out of Child Modules

Configure providers in the root; child modules only declare required_providers. That keeps modules reusable across regions and accounts, and lets you remove module instances cleanly.

Mirror Providers for Reliability

Large organizations use a provider mirror or cache (provider_installation in the CLI config, or terraform providers mirror) so CI isn't dependent on registry availability and downloads are fast.

Common Mistakes

Hard-Coded Credentials

Not Committing the Lock File

Without .terraform.lock.hcl, each init may select a different provider version within your constraints. Plans then differ between a laptop and CI for no visible reason.

Provider Configuration Depending on Resources Created in the Same Run

Configuring a Kubernetes provider from a cluster that the same apply creates often fails or behaves unpredictably, because provider configuration is evaluated early. Split cluster creation and in-cluster resources into separate configurations or stages.

FAQ

What's the difference between a provider and a module?

A provider is a plugin binary that knows how to call a platform's API and defines resource types (aws_s3_bucket). A module is Terraform code that combines resources, possibly from several providers, into a reusable unit. Modules use providers; providers don't use modules.

Can I write my own provider?

Yes, in Go using the Terraform Plugin Framework. It's worthwhile for internal platforms with APIs you want managed declaratively. For one-off API calls, the http data source or community "REST API" providers may be enough.

Do providers work with OpenTofu?

Yes. OpenTofu uses the same provider protocol and its own registry mirroring the same providers, so nearly all providers work unchanged.

Why does my plan show changes I didn't make after upgrading a provider?

New provider versions can change defaults, add computed attributes, or normalize values differently. Read the changelog, run plan in a lower environment first, and use lifecycle { ignore_changes = [...] } only for attributes that are legitimately managed elsewhere.

Related Topics

References