3.2 How Terraform Uses Providers

Key Takeaways

  • Providers are plugins that translate HCL into vendor API calls; Terraform core does not implement AWS or Azure APIs
  • Resource types belong to a provider — aws_instance is implemented by hashicorp/aws, not by Terraform core
  • Configure a provider with a provider block and pass credentials through environment variables, a shared credentials file, or an IAM role — never hardcode secrets in .tf files
  • During plan the provider reads and refreshes remote objects; during apply it creates, updates, or deletes them
  • A resource that omits the provider meta-argument uses the default unaliased configuration whose local name matches the resource type prefix
Last updated: August 2026

3.2 How Terraform Uses Providers

Quick Answer: A provider is a plugin. Terraform core does not know how to call AWS or Azure. The plugin translates your HCL (aws_instance, azurerm_resource_group) into that vendor's API. You configure the plugin with a provider block and feed it credentials from the environment, a shared credentials file, or a cloud role — not from secrets pasted into Git.

Objective 2b on Terraform Associate (004) is conceptual: describe how Terraform uses providers. The exam is not asking you to memorize every AWS argument. It is asking whether you can separate Terraform core from the plugin, name what the plugin contributes (resource types, data sources, provider-defined functions, API calls), and configure that plugin without leaking credentials.

Providers are plugins, not Terraform core

HashiCorp's language docs open with the same sentence you should be able to repeat: Terraform relies on plugins called providers to interact with remote systems. Each provider adds resource types and data sources. Without providers, Terraform cannot manage any infrastructure.

That split is the highest-yield 004 trap in this objective:

  • Terraform core parses HCL, builds the dependency graph, reads and writes state, computes a plan, and calls plugin RPCs.
  • The provider plugin implements aws_instance, talks HTTPS to ec2.amazonaws.com, and returns attributes such as id and private_ip.

Terraform 1.12 does not contain an AWS SDK. Upgrading the terraform binary does not give you new aws_* resource arguments. New AWS features arrive when you upgrade the hashicorp/aws provider (see 3.1).

Most providers target a cloud or SaaS API. Some, such as hashicorp/random and hashicorp/archive, are local utilities: they still install as plugins, but they do not call a public cloud. A root module that mixes aws_instance with random_id is using two plugins, not a special core feature.

Resource types belong to providers

Every managed resource type is implemented by exactly one provider. The first segment of the type is the preferred local name:

Resource or data typeProvider local nameTypical source
aws_instance, aws_vpcawshashicorp/aws
random_id, random_passwordrandomhashicorp/random
kubernetes_deploymentkuberneteshashicorp/kubernetes
datadog_monitordatadogdatadog/datadog

There is no core resource type that spans two clouds. Multi-cloud on Terraform means multiple providers in one configuration, each managing its own types. That idea starts in objective 1c and becomes configuration in 2c (section 3.3).

If a resource omits the provider meta-argument, Terraform takes the first word of the type (aws from aws_instance) and looks for a provider configuration with that local name. That is why using the preferred local name in required_providers lets you skip provider = aws on almost every resource.

The provider block configures the plugin

required_providers answers "which plugin, which version." The provider block answers "how should that plugin authenticate and which account or region should it target."

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

The body of provider "aws" is defined by the AWS provider, not by Terraform core. region is an AWS argument. Azure uses features and subscription_id. Kubernetes uses config_path or in-cluster settings. Read the provider's own registry docs for the argument list.

If you omit the provider block entirely, Terraform still creates an empty default configuration for that local name. Providers that can discover everything from the environment (region plus credentials) often work with that empty block. Providers that require arguments fail at plan time because the empty default is incomplete.

You may use expressions in a provider block, but only values Terraform knows before apply: input variables, locals, and other literal configuration. You cannot set region = aws_instance.web.availability_zone — that attribute is computed during apply, so the provider would not be configurable when the run starts.

Default versus explicit provider

  • The provider block without alias is the default configuration for that local name.
  • Resources, data sources, and modules that do not set a provider meta-argument use that default.
  • An explicit provider = aws.west (covered in 3.3) overrides the default for one block.

On 004, "default provider" means the unaliased configuration, not "the provider HashiCorp likes best."

Authentication — do not hardcode secrets

Providers need credentials. HashiCorp's provider-block reference states that many providers accept shell environment variables or other alternate sources so credentials stay out of version-controlled configuration. That sentence is exam-complete.

For hashicorp/aws, the usual assignment methods are:

  1. Environment variablesAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_PROFILE, AWS_REGION / AWS_DEFAULT_REGION.
  2. Shared credentials and config files~/.aws/credentials and ~/.aws/config, optionally selected with profile in the provider block or AWS_PROFILE.
  3. Runtime roles — an EC2 instance profile, ECS task role, or Kubernetes IRSA role. The provider obtains a temporary session from the metadata service.
  4. Provider argumentsaccess_key, secret_key, token, shared_credentials_files, assume_role. These work, but secrets written as string literals in .tf files get committed.

Do not do this in a shared repository:

provider "aws" {
  region     = "us-east-1"
  access_key = "AKIA..."   # never commit this
  secret_key = "wJalr..."  # never commit this
}

The 004 answer is: pass credentials through the environment, a shared credentials file, or a cloud role. The provider block should carry non-secret settings such as region. Sensitive-data handling goes deeper in objective 4h; here you only need the rule that provider secrets do not belong in Git.

HCP Terraform typically injects credentials as environment variables or through a run's variable set, then still uses the same provider "aws" { region = ... } block.

Data sources and provider-defined functions

At a high level, a provider contributes more than managed resources:

  • Managed resources (resource "aws_instance" "web") — Terraform creates, updates, and destroys the remote object and records a binding in state.
  • Data sources (data "aws_ami" "al2023") — the provider reads an existing object during plan and exposes attributes for the rest of the configuration. Terraform does not create that AMI.
  • Provider-defined functions (Terraform 1.8+, so they are in scope for 1.12) — offline helper functions the plugin exposes. You call them with provider::<local-name>::<function-name>(...), for example provider::aws::arn_parse(var.arn) or the built-in provider::terraform::encode_tfvars({ example = "Hello!" }).

You cannot define custom functions in HCL itself. If the exam mentions a provider:: call, that is a provider-defined function, not a built-in such as length() or lookup().

Plan versus apply: which API calls happen when

Both phases load the provider plugin. They do not do the same work.

PhaseWhat Terraform core asks the provider to doTypical vendor API traffic
terraform plan (and the refresh that precedes it)Read current remote objects and data sources so the plan compares configuration with realityDescribe* / GET-style reads
terraform applyExecute the planned create, update, or delete, then read the result back into stateCreate* / Modify* / Delete* plus follow-up reads

Prior to an operation, Terraform refreshes state by asking each provider to read the objects already bound in state. That is why a console-deleted EC2 instance shows up as a create on the next plan: the AWS provider's read returned "not found," core updated the planned action, and apply will call create. Apply is not the first time the plugin runs.

A plan that only interpolates locals and never touches a resource still needs initialized providers if the configuration references those providers. terraform init must have succeeded first.

004 traps for objective 2b

  • Terraform core does not implement AWS or Azure APIs.
  • Upgrading Terraform CLI does not add new aws_* arguments; upgrading the AWS provider does.
  • Providers run during plan and apply, not only during apply.
  • Data sources are provider features that read existing objects; they are not a second Terraform core language.
  • Empty default provider configurations work only when the plugin can discover required settings from the environment.
Loading diagram...
Terraform core vs provider plugin during plan and apply
Test Your Knowledge

Which statement correctly describes Terraform core and cloud APIs on Terraform 1.12?

A
B
C
D
Test Your Knowledge

What is the safest way to give the AWS provider credentials in a Git-tracked Terraform 1.12 configuration?

A
B
C
D
Test Your Knowledge

During terraform plan versus terraform apply, what does a provider typically do?

A
B
C
D