skip to content

Providers & Registry

Providers are the plugins that turn HCL into API calls, and pinning, aliasing and authenticating them is the unglamorous work that keeps a config reproducible. Interviewers ask about aliases because multi-region and multi-account setups make them unavoidable.

part ofTerraformoverview, primer and where to startread it →
on this pageshow

questions

16

In Terraform, how do you create resources in two AWS regions from a single configuration, and how does an individual resource choose the second region?

level: juniorimportance: must knowfreq 70%

answer

  1. region belongs to the provider, not the resource
  2. two regions means two configurations
  3. the second block gets a name
  4. provider = aws.eu on the resource
  5. unaliased block is the implicit default

basics

~20 s

Declare a second aws provider block with an alias argument and its own region, then set the meta-argument provider = aws.<alias> on each resource that belongs in that region. Resources with no provider argument use the unaliased default block.

solid answer

~40 s

Region is a property of the *provider configuration*, not of the resource, so touching two regions means having two configurations. You declare one `provider "aws"` block without an `alias` — that becomes the default for every `aws_*` resource — and a second block with `alias = "eu"` and `region = "eu-west-1"`. Any resource or data source that should land in the second region gets the `provider` meta-argument pointing at it: `provider = aws.eu`, written as an unquoted reference rather than a string. Terraform records which provider configuration owns each resource in state, so a resource does not silently move regions if you edit the blocks. Child modules do not inherit aliased configurations — those must be passed in explicitly with the module's `providers` argument.

code

hcl · 17 lines
hcl
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "eu"
  region = "eu-west-1"
}

resource "aws_s3_bucket" "primary" {
  bucket = "example-primary-bucket"
}

resource "aws_s3_bucket" "replica" {
  provider = aws.eu
  bucket   = "example-replica-bucket"
}

go deeper

for a junior

Be able to write the two provider blocks from memory: one plain, one with alias and its own region, then provider = aws.eu on the resource. Say clearly that region is set on the provider, not the resource.

for a middle

Explain that the unaliased block is the implicit default chosen from the resource type prefix, that aliased ones are opt-in only, and that data sources need the same argument or they query the wrong region.

for a senior

Show that state binds each resource to a provider configuration, so removing or renaming a block strands existing resources, and discuss when many aliases in one root module beat one parameterised root module applied per region.

for a principal

Own the estate-level call: aliases put several regions in one state and one blast radius, while a region-per-root-module layout trades verbosity for isolated failure domains and independent apply cadence. Be ready to justify which one your organisation standardises on.

## The problem aliases solve In the AWS provider, `region` is an argument of the **provider configuration**, not of the resource. `aws_s3_bucket` has no `region` argument you can set per bucket. So a configuration that has to create something in `us-east-1` and something else in `eu-west-1` needs *two provider configurations* of the same provider — and therefore a way to tell them apart. That way is `alias`. ## Default vs aliased configurations A `provider "aws"` block with no `alias` is the **default configuration** for that provider inside the module where it is written. Terraform picks a provider for each resource from the resource type's prefix: `aws_instance` implies the `aws` provider, `google_compute_instance` implies `google`. With no further instruction it uses the default configuration. (If you never declare a provider block at all, Terraform implies an empty one, which for AWS means everything comes from environment variables and the shared config file.) Adding `alias = "eu"` creates an **additional** configuration that is never chosen automatically. Something must ask for it by name. ```hcl provider "aws" { region = "us-east-1" } provider "aws" { alias = "eu" region = "eu-west-1" } ``` ## Selecting a configuration on a resource Every resource and data source accepts the `provider` meta-argument. Its value is a **reference**, of the form `<provider name>.<alias>` — unquoted, not a string, and not an expression: ```hcl resource "aws_s3_bucket" "primary" { bucket = "example-primary" # default provider -> us-east-1 } resource "aws_s3_bucket" "replica" { provider = aws.eu # -> eu-west-1 bucket = "example-replica" } ``` The same argument works on `data` blocks, which matters more than people expect: a `data "aws_ami"` lookup without `provider = aws.eu` will search the *default* region and hand you an AMI ID that does not exist in the other one. ## What Terraform remembers State records, per resource instance, which provider configuration manages it. That is why the association is not cosmetic: if you delete or rename a provider configuration that existing resources still point at, Terraform refuses to plan with a "Provider configuration not present" error, because it no longer knows how to reach those objects even to destroy them. Removing a region is therefore a two-step job — destroy or move the resources first, then delete the block. ## You may have as many as you need — but they are static Aliases are just names, so a replicated three-region layout is three blocks plus a default. What you cannot do is compute the choice: `provider = aws[var.region]` is invalid, because provider configurations are resolved before expressions are evaluated. The provider's *arguments* can use variables (`region = var.region` is fine), which is why the common alternative to many aliases is one root module parameterised by region, applied once per region against separate state. ## Aliases and modules Default (unaliased) configurations are inherited by child modules automatically. Aliased ones are not. A `module` block that needs one passes it explicitly through the `providers` map, and the child declares that it expects it. A reusable module should never declare its own `provider` block — that hard-codes the region into the module and blocks the caller from using `count` or `for_each` on it. ## Interview framing The crisp version of this answer is three sentences: region lives on the provider, a second region means a second provider configuration distinguished by `alias`, and a resource opts into it with `provider = aws.eu`. Everything else — cross-account variants using `assume_role` per alias, passing them into modules, the removal constraint — hangs off that skeleton.

  • If a resource has no provider argument, how does Terraform decide which configuration manages it?
    It derives the provider from the resource type prefix — `aws_instance` means the `aws` provider — and uses that provider's default (unaliased) configuration in the module where the resource is declared. If no provider block exists at all, Terraform implies an empty configuration, so the AWS provider falls back to environment variables and the shared config file.
  • Does the provider meta-argument work on data sources too, and why does that matter?
    Yes, and forgetting it is a classic bug. A `data "aws_ami"` or `data "aws_vpc"` block without `provider = aws.eu` queries the default region, so you get an ID that is meaningless in the other region and the apply fails — or worse, silently attaches the wrong object. Whenever a resource is aliased, check its lookups are aliased too.
  • What happens if you delete an aliased provider block while resources in state still reference it?
    Terraform fails planning with a "Provider configuration not present" error. State records the provider configuration for each resource, and without it Terraform cannot even destroy them. You must destroy or move those resources while the configuration still exists, then remove the block.

saying these in an interview costs you the question

  • Thinks one provider block can cover several regions at once
  • Looks for a region argument on the resource itself
  • Quotes the reference, writing provider = "aws.eu"
  • Assumes child modules inherit aliased providers automatically
  • Says you must run Terraform separately for each region

context

open as a page

In a Terraform aws provider block, why should you not set access_key and secret_key, and where does the provider get credentials instead?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Keys written into HCL get committed, cloned and copied, and cannot be rotated per run. Omit access_key and secret_key; the AWS provider then resolves credentials itself from environment variables, the shared AWS config files, or an attached instance or CI role.

open as a page

In a Terraform configuration, what does the `required_providers` block do, and why does each entry need a `source` as well as a `version`?

level: juniorimportance: must knowfreq 74%

basics

~20 s

required_providers declares every provider plugin a Terraform module depends on. The source argument is the registry address that tells Terraform exactly which plugin to download; version constrains which releases are acceptable. Without an explicit source, Terraform assumes the hashicorp namespace.

open as a page

A child Terraform module has to create resources in a second AWS region. How does the module get that provider configuration, and what must the child module declare to accept it?

level: middleimportance: must knowfreq 58%

basics

~20 s

The calling module passes it with the module block's providers map, for example providers = { aws = aws.us, aws.replica = aws.eu }. The child declares the extra slot with configuration_aliases inside its required_providers entry, then selects it per resource.

open as a page

What does Terraform's `.terraform.lock.hcl` record, and should it be committed to version control?

level: middleimportance: must knowfreq 70%

basics

~20 s

The dependency lock file records, per provider, the exact version Terraform selected, the constraints it was selected under, and checksums of the provider packages. It belongs in Git: it is what makes every laptop and CI runner install byte-identical plugins.

open as a page

How would you manage resources in two different AWS accounts from a single Terraform root module, and what does that layout cost you?

level: middleimportance: should knowfreq 48%

basics

~20 s

Declare one aliased aws provider per account, each with its own assume_role block naming a role in that account, and select the right alias on each resource or pass it into modules. The cost is that both accounts share one state file and one blast radius.

open as a page

What does an assume_role block inside Terraform's aws provider do, and which credentials does it need before it can work?

level: middleimportance: should knowfreq 52%

basics

~20 s

It tells the provider to take the credentials it already resolved, call AWS STS to assume the named role, and use the temporary session credentials for every API call. It needs working base credentials that the target role's trust policy permits to assume it.

open as a page

Your Terraform aws provider block sets profile = "dev". It plans fine on a laptop but fails in CI, where credentials are supplied as environment variables. Why, and in what order does the AWS provider look for credentials?

level: middleimportance: should knowfreq 56%

basics

~20 s

Arguments written in the provider block win over everything else, so profile = "dev" makes the provider look for a shared-config profile that does not exist on the CI runner and ignore the environment variables. Remove the argument and let AWS_PROFILE or the environment supply it.

open as a page

In a Terraform version constraint, what does the pessimistic operator `~>` allow, and how does `~> 5.2` differ from `~> 5.2.0`?

level: middleimportance: should knowfreq 62%

basics

~20 s

~> allows increments of the rightmost component you wrote, and nothing above it. ~> 5.2 means 5.2.0 up to but excluding 6.0.0, so any 5.x minor. ~> 5.2.0 means 5.2.0 up to but excluding 5.3.0, so patches only.

open as a page

Why should a reusable Terraform module never contain its own provider block, and what specifically breaks if it does?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A module that configures its own provider cannot be used with count, for_each or depends_on, hides region and credentials from the caller, and cannot be removed cleanly because Terraform still needs that configuration to destroy what it created. Declare requirements instead and receive configurations from the caller.

open as a page

Your CI runs Terraform against AWS with no long-lived access keys stored anywhere. How does the AWS provider obtain credentials in that setup, and what has to exist for it to work?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The CI platform issues the job a short-lived signed OIDC token, and the AWS provider exchanges it for a temporary role session through STS web-identity federation. It needs the CI issuer registered as an OIDC identity provider in the account and a role whose trust policy accepts tokens from that issuer.

open as a page

A teammate widened the AWS provider constraint in `required_providers` from `~> 5.30` to `~> 5.80`, but CI's `terraform plan` still runs provider 5.31.0. Why, and how do you move it forward safely?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Because .terraform.lock.hcl still records 5.31.0, and 5.31.0 still satisfies the widened constraint, so terraform init reuses it. Run terraform init -upgrade locally, review the lock-file diff and the resulting plan, then commit the updated lock file.

open as a page

Terraform `init` fails on a Linux CI runner with a checksum error against a `.terraform.lock.hcl` generated on a developer's Mac. What causes this, and what is the durable fix?

level: seniorimportance: should knowfreq 42%

basics

~20 s

The lock file records checksums for provider packages it has actually seen. If it only carries hashes for the developer's platform, a runner on a different OS or architecture downloads a package matching nothing recorded and init aborts. Fix it with terraform providers lock -platform=... for every platform in use.

open as a page

What does the default_tags block in Terraform's aws provider do, and what does it not cover?

level: middleimportance: nice to knowfreq 40%

basics

~20 s

It sets tags applied to every taggable resource that provider instance manages, so cost and ownership tags stop being copy-pasted per resource. It does not reach resources managed elsewhere, data sources, or tagging shapes that are not a plain tags map.

open as a page

What does `required_version` in Terraform's `terraform` block constrain, and how is it enforced differently from a provider version constraint?

level: middleimportance: nice to knowfreq 38%

basics

~20 s

required_version constrains the Terraform CLI itself, not a provider. Terraform checks it before doing any work and errors out if the running binary is outside the range. Unlike providers, no lock file pins the CLI — you only get a range check.

open as a page

In Terraform, why can't you choose a provider alias from a variable — for example provider = aws[var.region] — and how do you deploy the same module across a list of regions instead?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

The provider meta-argument takes a static reference, not an expression: Terraform resolves provider configurations before evaluating variables, so an alias cannot be computed. Fan out by repeating the module block once per region with an explicit providers map, or by running one root module per region.

open as a page