Back to Blog

Terraform Providers & the Provider Block for the Terraform Associate Exam: required_providers, Version Constraints, Aliases & Authentication

A practitioner's deep dive into Terraform providers for the HashiCorp Terraform Associate exam — the required_providers block, source addresses, version constraints, the dependency lock file, provider aliases, and safe authentication — with copy-paste HCL and terraform init examples.

By Sailor Team , July 6, 2026

Ask ten people what a Terraform provider is and most will say “it’s the AWS bit.” That’s close enough to write your first config, but it’s not close enough to pass the HashiCorp Terraform Associate exam. Providers are the plugin layer that lets Terraform talk to anything with an API — cloud platforms, SaaS tools, Kubernetes, even your DNS host — and the exam tests whether you understand how they’re declared, versioned, authenticated, and configured more than once in a single project.

This guide walks through everything the exam expects you to know about providers, from the required_providers block to aliases and the dependency lock file, with HCL you can paste into a real project. If you want the full blueprint and logistics first, start with the Terraform Associate exam guide for 2026 and the 30-day study plan. Once you’re comfortable with providers, variables, outputs, and locals and state management are the natural next objectives.

What a Provider Actually Is

A provider is a plugin — a separate executable that Terraform downloads and runs — that implements the resources and data sources for a specific platform. Terraform Core knows nothing about EC2 instances or S3 buckets; it knows how to build a dependency graph, plan changes, and call out to plugins. The aws provider is what actually turns resource "aws_instance" "web" {} into API calls.

Every provider is published to a registry. The public Terraform Registry (registry.terraform.io) hosts them in three tiers you should recognize for the exam:

TierPublished byExamples
OfficialHashiCorpaws, google, azurerm, kubernetes
PartnerA verified technology partnerdatadog, cloudflare
CommunityIndividual maintainersmany niche providers

The key exam fact: Terraform does not ship with any providers built in. They are downloaded during terraform init, cached in the .terraform directory, and pinned in a lock file. Understanding that lifecycle is what most provider questions are really testing.

Declaring Providers: the required_providers Block

The modern, exam-correct way to declare which providers a configuration needs is the required_providers block, which lives inside the top-level terraform {} block:

terraform {
  required_version = ">= 1.5.0"

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

Two separate constraints are at work here, and the exam loves to blur them:

  • required_version constrains Terraform Core itself (the terraform binary).
  • Each entry under required_providers constrains a provider plugin.

A version mismatch on either one causes terraform init to fail with a clear error — but they are different mechanisms. If a question asks how to require Terraform 1.6+, the answer is required_version, not a provider constraint.

Provider Source Addresses

Notice source = "hashicorp/aws". That’s a source address, and its full form is:

[<HOSTNAME>/]<NAMESPACE>/<TYPE>
  • HOSTNAME — the registry host. Defaults to registry.terraform.io when omitted.
  • NAMESPACE — the organization on that registry (hashicorp).
  • TYPE — the provider’s short name (aws).

So hashicorp/aws is shorthand for registry.terraform.io/hashicorp/aws. If you use a provider from a private registry, you spell out the host: registry.example.com/mycorp/internal. Since Terraform 0.13, the source attribute is required for any provider that isn’t in the default hashicorp namespace, and it’s best practice to always include it. A frequent trap: assuming type alone (like just aws) is enough — Terraform needs the namespace to know whose AWS provider you mean.

Version Constraints: the Operators You Must Know

Version constraint syntax is one of the most reliably tested provider topics. Memorize these operators:

OperatorMeaningExample matches
= 5.31.0 (or just 5.31.0)Exact version only5.31.0
!= 5.30.0Any version except this oneeverything but 5.30.0
>= 5.0This version or newer5.0, 5.9, 6.2
<= 5.40This version or older5.40, 5.10, 4.0
> 5.0, < 6.0Range (comma = AND)5.1 through 5.99
~> 5.31Pessimistic: allow rightmost part to increment>= 5.31, < 6.0
~> 5.31.0Pessimistic at patch level>= 5.31.0, < 5.32.0

The pessimistic constraint operator ~> is the one candidates get wrong. The rule: it allows the rightmost specified component to increase, but nothing to its left.

  • ~> 5.31 means “5.31 or any later 5.x” → >= 5.31.0, < 6.0.0.
  • ~> 5.31.0 means “5.31.x patch releases only” → >= 5.31.0, < 5.32.0.

Multiple constraints are combined with commas and evaluated as a logical AND: >= 5.0, < 5.40 matches versions in that window. When several modules declare overlapping constraints, Terraform selects the newest version that satisfies all of them; if they can’t be reconciled, init errors.

The Dependency Lock File

When you run terraform init, Terraform selects a provider version that satisfies your constraints and writes it — plus cryptographic checksums — to a file called .terraform.lock.hcl in your working directory. This file is one of the most exam-relevant concepts introduced in modern Terraform.

# .terraform.lock.hcl (excerpt)
provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.31.0"
  constraints = "~> 5.0"
  hashes = [
    "h1:abc123...",
    "zh:def456...",
  ]
}

Key facts the exam tests:

  • Commit it to version control. The lock file guarantees every teammate and every CI run uses the exact same provider versions, making builds reproducible.
  • terraform init respects it. Once a version is locked, re-running init reuses that locked version even if a newer one is available within your constraints.
  • terraform init -upgrade is how you deliberately move to newer versions. It re-evaluates constraints, picks the newest allowed versions, and rewrites the lock file.
  • It records checksums for verification. If a downloaded provider’s checksum doesn’t match the lock file, init fails — a supply-chain safeguard.

If your team runs Terraform on multiple operating systems (say, macOS laptops and Linux CI runners), use terraform providers lock to record checksums for every platform, so the lock file doesn’t break on a machine whose hash isn’t listed:

terraform providers lock \
  -platform=linux_amd64 \
  -platform=darwin_arm64

Configuring a Provider: the provider Block

Declaring a provider says which plugin you need. The provider block configures how it behaves — region, endpoints, default tags, and so on:

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

  default_tags {
    tags = {
      Environment = "production"
      ManagedBy   = "terraform"
    }
  }
}

You can have a configuration with a required_providers entry but no provider block at all — the provider then runs with its defaults and whatever it reads from the environment. That’s common for providers like random or null that need no configuration.

Authenticating Providers Safely

The exam has a strong opinion here, and it aligns with real-world security: never hardcode credentials in your .tf files. A provider block like this is the wrong answer on the exam and a genuine security incident in practice:

# DON'T DO THIS — credentials in code get committed to Git
provider "aws" {
  region     = "us-east-1"
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

Instead, providers read credentials from the ambient environment. For AWS the standard, exam-friendly options are:

  • Environment variablesAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN.
  • Shared config/credentials files~/.aws/credentials and a named AWS_PROFILE.
  • Instance/role-based credentials — an EC2 instance profile or an assumed IAM role, so no static keys exist at all.

The principle generalizes: keep the provider block free of secrets and let the environment (or a role) supply them. If a question shows credentials embedded directly in HCL, that’s the option to reject.

Multiple Provider Configurations with alias

By default you get one configuration per provider. But real projects need more — deploy to two AWS regions, or manage two Kubernetes clusters. That’s what provider aliases are for, and they’re a favorite exam topic.

Define an additional configuration with the alias meta-argument:

# Default (un-aliased) configuration
provider "aws" {
  region = "us-east-1"
}

# Additional aliased configuration
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

Now select the aliased configuration in a resource using the provider meta-argument, with the <PROVIDER>.<ALIAS> reference syntax:

resource "aws_s3_bucket" "primary" {
  bucket = "my-app-primary"
  # uses the default us-east-1 provider implicitly
}

resource "aws_s3_bucket" "replica" {
  provider = aws.west          # explicitly use the west configuration
  bucket   = "my-app-replica"
}

Two facts to lock in:

  • A resource with no provider argument uses the default (un-aliased) configuration.
  • The reference is aws.west — the provider local name plus the alias, without quotes.

Passing Providers into Modules

When a resource lives in a child module, providers are passed explicitly through the providers argument on the module block:

module "replica_region" {
  source = "./modules/storage"

  providers = {
    aws = aws.west
  }
}

Inside the module, if it uses an aliased provider, it declares that expectation with configuration_aliases in its own required_providers:

# modules/storage/versions.tf
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      configuration_aliases = [aws.west]
    }
  }
}

The inheritance rule the exam tests: a child module automatically inherits the default provider configuration from its caller, but aliased configurations must be passed explicitly. If a module needs a non-default region and you forget the providers block, it will silently use the default configuration — or error if it declared a configuration_alias.

How terraform init Installs Providers

Providers are installed by terraform init, which:

  1. Reads required_providers to learn what’s needed.
  2. Checks .terraform.lock.hcl for already-selected versions.
  3. Downloads matching plugins from the registry into .terraform/providers/.
  4. Verifies checksums against the lock file.
  5. Writes or updates the lock file.

To avoid re-downloading the same provider for every project, set a plugin cache directory so Terraform reuses a shared local copy:

export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache"

For air-gapped environments, you can configure a filesystem or network mirror in the CLI config file (.terraformrc) so init pulls providers from an internal location instead of the public registry. You won’t be asked to write a mirror config, but you should recognize that these options exist for offline installation.

Common Provider Traps on the Exam

ScenarioThe trapThe right call
Requiring a Terraform versionUsing a provider version constraintUse required_version in terraform {}
~> 1.2.0 vs ~> 1.2Assuming they mean the same thing~> 1.2.0 locks the patch; ~> 1.2 allows any 1.x minor
Two regionsDuplicating the whole configOne provider + an alias, selected via provider =
Module in another regionExpecting automatic inheritanceAliased providers must be passed via providers = {}
Reproducible buildsIgnoring .terraform.lock.hclCommit the lock file to version control
Upgrading a providerEditing the lock file by handRun terraform init -upgrade
Credentials in a provider blockHardcoding keys in HCLUse env vars, profiles, or an IAM role

If you can navigate this table without hesitating, you’ve covered the bulk of what the provider objective throws at you. Pair it with a solid grasp of modules and count vs. for_each, since aliased providers and modules frequently appear in the same question.

Practice Providers Until Init Is Second Nature

Provider questions reward pattern recognition: you see a version constraint or an alias reference and you know the answer, rather than reasoning it out under time pressure. You build that reflex by writing small configs — declare two aliased providers, run terraform init, inspect the generated .terraform.lock.hcl, then run init -upgrade and watch it change — and by drilling realistic multiple-choice questions.

Sailor.sh’s Terraform Associate mock exams include provider-focused items covering required_providers, version constraint math, aliases, and the lock file, each with an explanation of why the answer is correct. You can gauge where you stand first with the free Terraform Associate practice questions, then reinforce the hands-on side with the Terraform commands cheat sheet. Treating providers as a topic to practice rather than read about is what turns it into one of your quickest, most reliable point sources on exam day.

Frequently Asked Questions

Where does the required_providers block go?

Inside the top-level terraform {} block. It’s a nested block, alongside required_version and backend configuration. Putting provider version constraints anywhere else (like inside the provider block) is invalid.

What’s the difference between the provider block and required_providers?

required_providers declares which provider plugins your configuration needs and their version constraints. The provider block configures how an installed provider behaves (region, tags, endpoints). You can have required_providers without a matching provider block, and providers that need no configuration often work that way.

Should I commit .terraform.lock.hcl to Git?

Yes. The dependency lock file pins exact provider versions and checksums so every machine and CI run gets identical plugins, making builds reproducible. Committing it is a best practice the exam expects you to know.

How do I upgrade a provider to a newer version?

Run terraform init -upgrade. It re-evaluates your version constraints, selects the newest allowed versions, downloads them, and rewrites the lock file. Simply editing the version number in required_providers and running plain init will not downgrade or bypass an already-locked newer selection the way -upgrade re-resolves everything.

When do I need a provider alias?

Whenever you need more than one configuration of the same provider — for example, deploying to two AWS regions or managing two separate accounts. Define the extra configuration with alias, then reference it with provider = aws.<alias> on resources or pass it to modules via providers = {}.

Do child modules inherit provider configurations?

They inherit the default (un-aliased) provider configuration from the calling module automatically. Aliased configurations are not inherited automatically — you must pass them explicitly with the providers argument on the module block, and the module declares them via configuration_aliases.

Can I put my AWS credentials in the provider block?

You can, but you should never do it — hardcoded credentials get committed to version control and are a security risk. Supply credentials through environment variables, a shared credentials file with a named profile, or an IAM role. On the exam, an option that embeds keys directly in HCL is the wrong answer.


Ready to make providers a strength? Drill realistic, explained questions with the Sailor.sh Terraform Associate mock exams, then map your full prep with the Terraform Associate exam guide.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now