Install and version Terraform providers
Declaring provider requirements
A provider block configures a plugin, but Terraform must first know which plugin to fetch, and that is what a required_providers block declares, nested inside the top-level terraform block:
terraform {
required_version = "~> 1.12"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.1"
}
}
}
Each entry maps a local name (the key, here aws and random) to an object holding a source address and an optional version constraint[1]. Placing required_providers at the top level of a file, or inside a provider block, is invalid: it belongs only in the terraform block. Naming that source, constraining the version, and letting terraform init install and lock the result are the three moves this page walks through.
The source address is not a URL
A source value is a registry address, not a download link. It has the form [HOSTNAME/]NAMESPACE/TYPE, three slash-delimited parts[1]. hashicorp/aws names the aws type in the hashicorp namespace; the hostname is omitted, so Terraform resolves it against the default public registry registry.terraform.io. Writing the hostname explicitly (registry.terraform.io/hashicorp/aws) is legal but redundant for public providers.
The local name is a handle, not the type
The key you choose is the provider's local name, and it is what the rest of the module uses. It prefixes that provider's resource types (aws_instance comes from the provider whose local name is aws) and it is the label a provider "aws" block configures. By convention the local name matches the source TYPE, and using the provider's preferred local name[1] keeps a configuration clear, but the two are technically independent and can differ.
required_version is a separate lever
Alongside required_providers, the terraform block accepts required_version[2], which constrains the Terraform CLI (core) itself rather than any provider, using the same constraint syntax covered in the next section. If the running CLI does not satisfy it, Terraform prints an error and exits without taking any action. Keep the two straight: required_version gates the binary, and each version under required_providers gates one plugin.
Version constraints and the ~> operator
A version constraint decides which releases Terraform may select, and it is always a quoted string with the operator and the number both inside the quotes. version = ">= 3.1" is valid; version >= 3.1 and version = >= 3.1 are syntax errors[3].
The operators
Terraform supports seven constraint operators[3]: = (or no operator) for an exact version, != to exclude one, the comparisons >, >=, <, <=, and the pessimistic ~>. Separate several conditions with commas and Terraform combines them with logical AND, so version = ">= 1.2.0, < 2.0.0" means at least 1.2.0 and below 2.0.0, not either bound. Use the decision tree above to pick an operator; this section explains what each one means.
The ~> pessimistic operator
~> allows only the right-most component you wrote to increment, which makes it the workhorse for taking patch updates without breaking changes. The exact behavior is worth memorizing, because the exam probes it directly:
| Constraint | Allows | Rejects |
|---|---|---|
~> 1.0.4 | 1.0.5, 1.0.10 (any 1.0.x at or above 1.0.4) | 1.1.0 |
~> 1.1 | 1.2, 1.10 (any 1.x at or above 1.1) | 2.0 |
Read it as: the last number you pin is the one allowed to grow. ~> 1.0.4 fixes the minor at 0 and lets the patch float, while ~> 1.1 fixes the major at 1 and lets the minor float.
Pre-release versions are opt-in
A pre-release such as 1.2.0-beta (a version carrying a dash suffix) is never selected by a range operator[3]. >, >=, <, <=, and ~> all skip it. The only way to select one is to request that exact version with =, for example version = "= 1.2.0-beta". A plain >= 1.2.0 therefore passes over 1.2.0-beta and waits for the stable 1.2.0.
Installation sources and the lock file
Declaring a provider does not fetch it; terraform init[4] does. During init Terraform scans the configuration for every provider it references, selects a version that satisfies the constraints, downloads the matching plugin binary into the working directory, and records what it chose. You must run init before plan or apply can use any provider.
Where providers come from
Providers ship as pre-built plugin binaries; Terraform never compiles provider source code at run time. By default init downloads from the origin registry (registry.terraform.io), but a provider_installation block in the CLI configuration[5] can redirect that:
direct: the default, fetching each provider from its origin registry over the network.filesystem_mirror: read providers from a local directory, which is how air-gapped installs work.network_mirror: read providers from an HTTPS server, regardless of their origin registry.
Independently, a shared plugin cache set through plugin_cache_dir or the TF_PLUGIN_CACHE_DIR environment variable[5] lets init reuse an already-downloaded binary instead of fetching it again. The -plugin-dir=PATH flag forces init to read plugins only from one directory, as if it were the sole filesystem_mirror.
The dependency lock file
Each init creates or updates .terraform.lock.hcl[6] in the working directory. It records the exact provider versions selected together with their checksums (hashes), so a teammate or CI system running init installs the same plugins. Commit this file to version control, exactly as you would the configuration itself.
Two limits matter on the exam. First, the lock file tracks providers only: it does not remember module versions, so Terraform always re-selects the newest module version allowed by that module's own constraint. Second, a plain terraform init reuses the versions already recorded in the lock file even when newer allowed releases exist. To move forward you run terraform init -upgrade[4], which re-evaluates the constraints, selects newer permitted versions, and rewrites the lock file.
How this appears on the exam
This objective is tested less by definitions than by spotting the plausible-but-wrong option. The recurring traps, each a restatement of a rule established above:
sourcelooks like a URL. A stem may offersource = "https://github.com/hashicorp/terraform-provider-aws". It is wrong:sourceis a registry address[HOSTNAME/]NAMESPACE/TYPE, and omitting the hostname impliesregistry.terraform.io, not that a URL is expected.required_versionversus providerversion. A stem blurs the two.required_versionin theterraformblock constrains the Terraform CLI; theversioninside arequired_providersentry constrains that one plugin. "Which Terraform binary can run this?" is the former; "which AWS provider release?" is the latter.~>arithmetic. Expect "does~> 1.1allow 2.0?" (no) or "does~> 1.0.4allow 1.1.0?" (no). Re-derive from the rule: only the right-most written component may increment.- When providers download. The answer is
terraform init, neverplanorapply. A plain re-run ofinitdoes not upgrade; onlyterraform init -upgradere-selects newer versions. - What the lock file locks.
.terraform.lock.hclpins providers and their checksums, not modules. "Commit it to version control" is correct; "it also pins module versions" is the distractor. - AND, not OR. Comma-separated conditions such as
">= 1.2.0, < 2.0.0"must all hold at once; reading them as alternatives is the wrong answer.
Provider version constraint operators
| Aspect | Exact `= x.y.z` | Pessimistic `~> x.y` | Range `>= x, < y` |
|---|---|---|---|
| Meaning | Only that one version | Right-most component may increment | Any version inside the bounds |
| Example allows | `= 5.2.0` selects only 5.2.0 | `~> 1.1` allows 1.2 and 1.10 | `>= 1.2.0, < 2.0.0` allows 1.9.0 |
| Example rejects | Every other version | `~> 1.1` rejects 2.0; `~> 1.0.4` rejects 1.1.0 | Rejects 2.0.0 and 1.1.0 |
| Pre-release `-beta` | Matched only by exact `=` | Never matched | Never matched |
Decision tree
Sharp facts the exam loves — give these one last read before exam day.
Cheat sheet
Sharp facts the exam loves — scan these before test day.
- required_providers lives inside the terraform block
The
required_providersblock must be declared inside the top-levelterraformblock, and each entry maps a module-local name to an object containing asourceargument and an optionalversionargument. It is not valid at the top level of a file or inside aproviderblock.Trap Placing required_providers at the top level of the file or inside a provider block instead of nested in the terraform block.
8 questions test this
- A module author is writing the `required_providers` block inside the `terraform` block and wants each provider entry structured the way the language requires. For a provider they will reference locall
- An engineer moves the `required_providers` block into their `provider "aws"` block, reasoning that a provider's requirements belong next to that provider's configuration. When they run `terraform init
- In a module's `required_providers` block, an author writes `mycloud = { source = "mycorp/mycloud", version = "~> 1.0" }`. A reviewer asks what the `mycloud` key on the left side of that mapping actual
- An engineer opens a new Terraform configuration and needs to declare that the root module depends on the `aws` and `random` providers with pinned versions. They are unsure where in the configuration t
- A `terraform` block contains both `required_version = ">= 1.8.0"` and a `required_providers` entry `aws = { source = "hashicorp/aws", version = ">= 5.30.0" }`. A teammate asks what each of these two v
- An engineer's configuration fails to initialize. At the top level of `main.tf`, outside every other block, they have written a standalone `required_providers { ... }` block listing their providers, an
- A configuration contains a `provider "aws" { region = "us-east-1" }` block but no `required_providers` entry. The author believes the `provider` block already declares the module's dependency on the A
- In a `required_providers` entry, an engineer declares `random = { source = "hashicorp/random" }` and deliberately omits the `version` argument. A teammate warns that `terraform init` will reject the e
- Provider source address format
A provider
sourceaddress has the form[HOSTNAME/]NAMESPACE/TYPE(for examplehashicorp/aws); when the hostname is omitted Terraform defaults to the public registryregistry.terraform.io.Trap Thinking
sourceis a download URL, or that the hostnameregistry.terraform.iomust always be written explicitly.10 questions test this
- A module author is writing the `required_providers` block inside the `terraform` block and wants each provider entry structured the way the language requires. For a provider they will reference locall
- During `terraform init`, Terraform needs to install a provider declared with `source = "hashicorp/random"`, which includes no hostname segment. From which registry does Terraform download this provide
- A source address is written in full as `registry.terraform.io/hashicorp/aws`. A teammate is unsure what the first segment, `registry.terraform.io`, before the two slashes actually specifies in a provi
- A new team member argues that because a provider `source` string "points somewhere," they should be able to set `source = "https://github.com/hashicorp/terraform-provider-aws"` so Terraform pulls the
- Your platform team publishes an internal provider through a private registry your company hosts at `terraform.example.com`. A new engineer copies a public example and writes `source = "examplecorp/myc
- Two providers are declared in `required_providers`: one entry uses `source = "hashicorp/aws"` and another uses `source = "registry.terraform.io/hashicorp/aws"`. A reviewer asks whether these two sourc
- A configuration contains a `provider "aws" { region = "us-east-1" }` block but no `required_providers` entry. The author believes the `provider` block already declares the module's dependency on the A
- In a `required_providers` entry, an engineer declares `random = { source = "hashicorp/random" }` and deliberately omits the `version` argument. A teammate warns that `terraform init` will reject the e
- A colleague asks you to confirm which of the following is written in the correct format for a provider `source` address inside a `required_providers` entry. Which one is a validly-formed provider sour
- A teammate writes `source = "hashicorp/aws"` inside a `required_providers` entry and asks you to explain what the two slash-separated parts of that provider source address actually represent. How shou
- The local name is the required_providers key
The key of each
required_providersentry is the provider's local name, which is used to reference that provider elsewhere in the module (inproviderblocks and as the prefix of its resource types). By convention it matches the source TYPE but it can differ.Trap Assuming the local name must exactly equal the TYPE in the source address.
- terraform required_version setting
Inside the terraform block, required_version constrains which Terraform CLI/core version may run the configuration using the standard constraint operators (=, !=, >, >=, <, <=, ~>). It applies only to the Terraform binary and is distinct from the per-provider version constraints in required_providers; if the running CLI version does not satisfy the constraint, Terraform prints an error and exits immediately without taking any action.
Trap required_version constrains the Terraform CLI, not providers — provider versions are pinned separately in required_providers under the terraform block.
6 questions test this
- A platform team wants their root module to run on Terraform CLI 1.9 or any newer release in the 1.x line, while automatically refusing the 2.0 major release. They intend to express this with a single
- During a code review, a colleague claims that adding `required_version = ">= 1.9.0"` to the `terraform` block will force everyone to use at least version 1.9 of the AWS provider. A plan then starts on
- A `terraform` block contains both `required_version = ">= 1.8.0"` and a `required_providers` entry `aws = { source = "hashicorp/aws", version = ">= 5.30.0" }`. A teammate asks what each of these two v
- A CI runner has Terraform CLI v1.6 installed, but a configuration's `terraform` block sets `required_version = ">= 1.9.0"`. When the pipeline job runs `terraform plan` against this configuration, how
- A developer's laptop has Terraform CLI v1.7, while a project's `terraform` block sets `required_version = ">= 1.12.0"`. They expect that running `terraform init` will make Terraform fetch and switch t
- A module's `terraform` block declares several `required_providers` entries with pinned `version` constraints but does not include a `required_version` argument. A teammate assumes this means only the
- Version constraint operators
Provider version constraints support
=(or no operator, exact),!=(exclude),>,>=,<,<=, and~>. Multiple constraints separated by commas are combined with logical AND, for exampleversion = ">= 1.2.0, < 2.0.0".Trap Treating comma-separated constraints as OR rather than AND.
5 questions test this
- A reusable module author wants to declare the minimum provider version the module is known to work with — at least 2.5.0 — while still allowing all future releases, including new 3.x and 4.x major ver
- A team runs on the 1.x line of a provider but has confirmed that release 1.4.2 contains a regression they must avoid. They want a single version constraint that accepts any 1.x version except 1.4.2. W
- An engineer writes `version = "2.4.0"` for a provider, with no comparison operator before the number, assuming it sets a floor for acceptable versions. During review, the team debates what this constr
- A platform team pins a provider in the `required_providers` block with the constraint `version = ">= 3.0.0, != 3.2.0"` after finding that release 3.2.0 introduced a regression. During code review a co
- A platform engineer pins a provider in the required_providers block with the constraint `version = ">= 1.2.0, < 2.0.0"` so the team stays on the 1.x line. A colleague asks how Terraform interprets the
- The ~> pessimistic operator
The
~>operator allows only the right-most specified version component to increment:~> 1.0.4permits 1.0.5 and later 1.0.x but not 1.1.0, while~> 1.1permits 1.2 and later 1.x but not 2.0.Trap Believing
~> 1.1allows 2.0, or that~> 1.0.4allows 1.1.0.4 questions test this
- An engineer pins a provider with `version = "~> 2.3.1"` in `required_providers`, expecting Terraform to automatically pick up the upcoming 2.4.0 minor release once it is published. A teammate reviewin
- A reusable module author wants to declare the minimum provider version the module is known to work with — at least 2.5.0 — while still allowing all future releases, including new 3.x and 4.x major ver
- A root module owner wants a provider constraint that automatically accepts new minor and patch releases across the entire 3.x line as they ship, but that firmly blocks the next major release, 4.0, bec
- An engineer sets `version = "~> 1.1"` for a provider, expecting ongoing updates without breaking changes. A teammate reviewing the pull request asks precisely which provider versions this two-componen
- Version constraints are quoted strings
The version requirement is a string assigned to
versioninside the provider object, with the operator and number both inside the quotes, e.g.version = ">= 3.1". Forms such asversion >= 3.1orversion = >= 3.1are invalid syntax.Trap Writing the constraint without the
=assignment or without quotes, e.g.version >= 3.1.5 questions test this
- While editing the `required_providers` block for the AWS provider, an engineer needs to require at least version 3.1 of that provider. Reviewing four candidate lines, which one uses valid HCL syntax t
- A team runs on the 1.x line of a provider but has confirmed that release 1.4.2 contains a regression they must avoid. They want a single version constraint that accepts any 1.x version except 1.4.2. W
- An engineer adds `version = >= 3.1` to the provider entry inside `required_providers`, then runs `terraform init`, which fails with a syntax error. What is the correct diagnosis and fix for this versi
- During code review, a teammate proposes writing a provider version constraint as `version = >= "3.1"`, quoting only the number and leaving the operator outside the quotes. Which statement about correc
- An engineer copies a provider requirement from a blog post into the `required_providers` block and writes the line as `version ">= 4.2"`, then runs `terraform init`, which fails with a syntax error. W
- Pre-release versions need an exact match
Pre-release versions such as
1.2.0-betaare only selected when requested with an exact=constraint; they never satisfy range operators like>=,<, or~>.Trap Expecting
>= 1.2.0to select a1.2.0-betapre-release.- terraform init installs providers
terraform initis the command that downloads and installs the provider plugins required by the configuration into the working directory; it must be run beforeterraform planorterraform applycan use those providers.Trap Thinking providers are downloaded during
terraform plan/applyrather than duringterraform init.5 questions test this
- A developer new to Terraform assumes that when they run `terraform init`, Terraform clones each provider's Go source code from GitHub and compiles it locally before use. Which statement correctly desc
- A team stores its configuration in Git together with a committed `.terraform.lock.hcl`. When a run executes in HCP Terraform (formerly Terraform Cloud), how are the required provider plugins made avai
- A colleague clones a Terraform configuration that declares the `hashicorp/aws` provider in its `required_providers` block into a fresh working directory. Eager to preview changes, they run `terraform
- A configuration constrains its AWS provider with `version = "~> 5.0"`, and version 5.40 was just published. The selection currently recorded in `.terraform.lock.hcl` is 5.31. An engineer wants Terrafo
- An engineer adds a second provider, `hashicorp/tls`, to a configuration that was already initialized with only `hashicorp/aws`. Running `terraform plan` now fails, complaining about an uninitialized p
- Where Terraform installs providers from
Terraform can install providers directly from an origin registry (default
registry.terraform.io), from a local filesystem mirror (a plugins directory), from an HTTPS network mirror, or reuse a shared plugin cache (plugin_cache_dir/TF_PLUGIN_CACHE_DIR). Terraform never compiles provider source code at runtime.Trap Believing Terraform compiles provider source code at run time — providers are distributed as pre-built plugin binaries.
6 questions test this
- A team commits `.terraform.lock.hcl` generated on macOS. A colleague on Linux then runs `terraform init` in CI and it fails because the lock file contains no checksum matching the Linux provider packa
- A platform engineer runs Terraform across dozens of working directories on one CI worker and wants to avoid downloading the same provider plugin from the registry over and over, reusing a single local
- A developer new to Terraform assumes that when they run `terraform init`, Terraform clones each provider's Go source code from GitHub and compiles it locally before use. Which statement correctly desc
- A colleague clones a Terraform configuration that declares the `hashicorp/aws` provider in its `required_providers` block into a fresh working directory. Eager to preview changes, they run `terraform
- A platform engineer on a Linux workstation wants to set per-user CLI behavior - such as a shared provider plugin cache - that applies across every Terraform working directory, independent of any singl
- An engineer must run Terraform in an isolated network that cannot reach `registry.terraform.io`. They have copied the required provider packages onto an internal HTTPS server and want Terraform to fet
- The .terraform.lock.hcl dependency lock file
Running
terraform initcreates or updates.terraform.lock.hclin the working directory, recording the exact provider versions selected plus their checksums (hashes); this file should be committed to version control and does not lock module versions.Trap Assuming the lock file also pins module versions — it only locks providers.
6 questions test this
- A team commits `.terraform.lock.hcl` generated on macOS. A colleague on Linux then runs `terraform init` in CI and it fails because the lock file contains no checksum matching the Linux provider packa
- After running `terraform init`, an engineer sees a new `.terraform.lock.hcl` file. A teammate insists the file pins both the provider versions and the versions of any remote modules the configuration
- A platform team keeps its Terraform configuration in a shared Git repository, and after a colleague runs `terraform init` for the first time a new `.terraform.lock.hcl` file appears in the working dir
- A team stores its configuration in Git together with a committed `.terraform.lock.hcl`. When a run executes in HCP Terraform (formerly Terraform Cloud), how are the required provider plugins made avai
- A configuration constrains its AWS provider with `version = "~> 5.0"`, and version 5.40 was just published. The selection currently recorded in `.terraform.lock.hcl` is 5.31. An engineer wants Terrafo
- An engineer adds a second provider, `hashicorp/tls`, to a configuration that was already initialized with only `hashicorp/aws`. Running `terraform plan` now fails, complaining about an uninitialized p
- terraform init -upgrade
Without
-upgrade,terraform initre-selects the versions already recorded in.terraform.lock.hcleven if newer releases exist; runningterraform init -upgradereconsiders the constraints, selects newer allowed versions, and updates the lock file.Trap Expecting a plain
terraform initto upgrade providers to the newest matching version.