Initializing Enclave...

Fixing Terraform 'Provider Configuration Not Known Until Apply' with data.aws_ami

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 10–20 mins

TL;DR

  • What broke: Terraform cannot resolve the provider argument on data.aws_ami at plan time because it references a dynamic or computed value — the plan is dead before it starts.
  • How to fix it: Use only statically-defined provider aliases in provider arguments on data sources; never pass a computed resource attribute or module output as a provider reference.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing .tf block and get corrected HCL without sending your ARNs or account IDs to any server.

The Incident (What Does the Error Mean?)

You hit this during terraform plan or terraform apply:

╷
│ Error: Provider configuration not known until apply
│
│   on main.tf line 12, in data "aws_ami" "base":
│   12:   provider = aws.${var.target_region}
│
│ The provider configuration for this data source depends on a value that
│ cannot be determined until apply. Data sources that depend on unknown
│ provider configurations cannot be read during the plan.
╵

Immediate consequence: Terraform's two-phase execution model (plan → apply) requires that every provider reference be fully resolved during the graph construction phase — before a single API call is made. If the provider meta-argument on data.aws_ami points to anything computed (a for_each key derived from a resource, a dynamic local, a module output that isn't yet known), the entire plan aborts. You get zero diff output. No resources are evaluated. CI/CD pipeline dies here.


The Attack Vector / Blast Radius

This is not just an inconvenience — it is a pipeline blocker with cascading consequences:

  1. Multi-region AMI lookups break entirely. The most common trigger is attempting to fan out data.aws_ami across regions using a dynamic provider map. The moment that map key is computed rather than literal, every downstream aws_instance, aws_launch_template, and aws_autoscaling_group depending on that AMI ID is also unresolvable.

  2. Module consumers inherit the blast radius. If this pattern lives inside a reusable module and the calling root module passes a computed providers map, every environment (dev, staging, prod) that calls that module is broken simultaneously.

  3. State drift risk. Engineers under pressure sometimes run terraform apply -target to work around this. Partial applies against autoscaling infrastructure leave state files inconsistent with reality — a ticking clock for the next deploy.

  4. Security implication of the workaround: Hardcoding AMI IDs to escape the error (instead of fixing the provider reference) means you stop tracking the latest hardened/patched AMI. That AMI ID rots. Six months later you're launching instances from an AMI with unpatched CVEs.


How to Fix It

Root Cause

Terraform's provider reference in a data block must be a literal reference to a statically declared provider alias — not a string interpolation, not a for_each key, not a computed local. The Terraform core graph walker resolves provider edges before it evaluates resource or data source configurations.


Basic Fix — Static Provider Aliases

Stop interpolating the provider alias. Declare explicit aliases and reference them literally.

# BROKEN: dynamic provider reference
- variable "target_region" {
-   default = "us-west-2"
- }
-
- provider "aws" {
-   alias  = "dynamic"
-   region = var.target_region
- }
-
- data "aws_ami" "base" {
-   provider = aws.${var.target_region}  # computed — ILLEGAL
-   most_recent = true
-   owners      = ["amazon"]
-   filter {
-     name   = "name"
-     values = ["amzn2-ami-hvm-*-x86_64-gp2"]
-   }
- }

# FIXED: static alias, resolved at graph construction time
+ provider "aws" {
+   alias  = "us_west_2"
+   region = "us-west-2"
+ }
+
+ provider "aws" {
+   alias  = "us_east_1"
+   region = "us-east-1"
+ }
+
+ data "aws_ami" "base_us_west_2" {
+   provider    = aws.us_west_2   # static literal alias — valid
+   most_recent = true
+   owners      = ["amazon"]
+   filter {
+     name   = "name"
+     values = ["amzn2-ami-hvm-*-x86_64-gp2"]
+   }
+ }
+
+ data "aws_ami" "base_us_east_1" {
+   provider    = aws.us_east_1
+   most_recent = true
+   owners      = ["amazon"]
+   filter {
+     name   = "name"
+     values = ["amzn2-ami-hvm-*-x86_64-gp2"]
+   }
+ }

Enterprise Best Practice — Module-Level Provider Passing

For multi-region infrastructure at scale, pass providers explicitly into modules using the providers map at the module call site — where aliases are always statically known.

# Root module — provider aliases are static here
+ provider "aws" {
+   alias  = "primary"
+   region = "us-east-1"
+ }
+
+ provider "aws" {
+   alias  = "dr"
+   region = "eu-west-1"
+ }

# Module call — providers map uses static alias references
- module "ami_lookup" {
-   source          = "./modules/ami"
-   region_provider = "aws.${local.computed_region}"  # ILLEGAL
- }

+ module "ami_lookup_primary" {
+   source = "./modules/ami"
+   providers = {
+     aws = aws.primary   # static — Terraform resolves this at graph time
+   }
+ }
+
+ module "ami_lookup_dr" {
+   source = "./modules/ami"
+   providers = {
+     aws = aws.dr
+   }
+ }

# Inside ./modules/ami/main.tf
+ terraform {
+   required_providers {
+     aws = {
+       source                = "hashicorp/aws"
+       configuration_aliases = [aws]
+     }
+   }
+ }
+
+ data "aws_ami" "base" {
+   most_recent = true
+   owners      = ["amazon"]
+   filter {
+     name   = "name"
+     values = ["amzn2-ami-hvm-*-x86_64-gp2"]
+   }
+ }

The module receives whichever regional provider the caller injects. The data.aws_ami inside the module inherits it without any dynamic reference. This is the only pattern that scales cleanly to N regions without hitting the plan-time resolution wall.


💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. terraform validate as a Pre-Commit Gate

terraform validate catches static provider reference errors before a plan is ever attempted. Wire it into pre-commit:

# .pre-commit-config.yaml
- repo: https://github.com/antonbabenko/pre-commit-terraform
  hooks:
    - id: terraform_validate
    - id: terraform_tflint

2. TFLint — terraform_required_providers Rule

Enable the ruleset that flags dynamic provider meta-arguments:

# .tflint.hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"
}

rule "terraform_required_providers" {
  enabled = true
}

3. Checkov Policy — Flag Computed Provider References

Checkov's CKV_TF_1 and custom graph policies can be extended to detect provider = "${...}" interpolation patterns in data sources. Add a custom check:

# checkov custom check skeleton
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck

class CheckStaticProviderAlias(BaseResourceCheck):
    # Flag any data source where provider value contains interpolation markers
    ...

4. OPA / Conftest Policy

# policy/no_dynamic_provider.rego
package terraform

deny[msg] {
  data_source := input.configuration.root_module.resources[_]
  data_source.mode == "data"
  # provider_config_key contains a computed reference pattern
  contains(data_source.provider_config_key, "${") 
  msg := sprintf("Data source '%s' uses a dynamic provider reference. Use static aliases only.", [data_source.address])
}

Run in CI: conftest test plan.json --policy policy/

5. Sentinel (Terraform Cloud/Enterprise)

For TFC/TFE shops, enforce at the policy-as-code layer so no run with dynamic provider references on data sources ever reaches the apply queue.


Bottom line: Every provider meta-argument on every data block must be a compile-time constant. If you need dynamic regional dispatch, the answer is always explicit module instantiation with static providers maps — not string interpolation.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →