skip to content

In Terraform, how does one root configuration read a value produced by a different configuration's state, and what has to exist on the producing side?

level: juniorimportance: should knowfreq 60%

answer

  1. outputs are the only doorway
  2. root module only, not child modules
  3. a data source, not a magic reference
  4. reads the file, needs bucket read

basics

~10 s

The producing configuration must declare a root-level output. The consumer adds a terraform_remote_state data source pointing at the producer's backend and reads data.terraform_remote_state.NAME.outputs.KEY. Only declared root outputs are exposed, never arbitrary resource attributes.

solid answer

~40 s

The handoff is one-directional and goes through outputs. The producing configuration declares an `output` at its **root** module — that is its published interface. The consuming configuration declares a `data "terraform_remote_state"` block naming the same backend and the same state location (for S3: bucket, key, region), and then reads `data.terraform_remote_state.network.outputs.vpc_id`. Two constraints trip people up. First, only root outputs are visible — a value produced inside a child module has to be re-exported at the root before anyone else can see it. Second, the output only exists after the producer has actually been applied; a fresh consumer plan against a producer that was never applied fails with a missing-output error rather than deferring. The consumer also needs backend read permission on that state object, because it reads the file directly.

code

hcl · 19 lines
hcl
# producer: network/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

# consumer: platform/main.tf
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "acme-tfstate"
    key    = "network/prod/terraform.tfstate"
    region = "eu-west-1"
  }
}

resource "aws_security_group" "app" {
  name   = "app"
  vpc_id = data.terraform_remote_state.network.outputs.vpc_id
}

go deeper

for a junior

Be able to name the two halves out loud: an output block on the producing side, a terraform_remote_state data source on the consuming side, read as data.terraform_remote_state.NAME.outputs.KEY.

for a middle

Explain the mechanics: only root outputs are exposed, the values come from the producer's last apply, and the read happens directly against the backend object with the consumer's own credentials.

for a senior

Show that you treat outputs as a published contract — renaming one breaks other teams' plans — and that you own the apply ordering across the seam yourself, since no graph edge spans two configurations.

for a principal

Own the policy question: whether teams may read each other's state at all, given that the grant is read of the whole file, and when to mandate an explicit published contract instead.

## Why there is a handoff at all Once an estate is split into more than one Terraform root configuration — say `network/` and `platform/` — each has its own state file. Terraform's graph and its interpolation only span a single configuration, so `aws_vpc.main.id` in `network/` is simply not an addressable expression inside `platform/`. Something has to carry the value across the seam, and Terraform's built-in answer is *outputs read through a data source*. ## The producing side: outputs are the public interface The producer declares an `output` block in its **root** module: ```hcl # network/outputs.tf output "vpc_id" { value = aws_vpc.main.id } output "private_subnet_ids" { value = [for s in aws_subnet.private : s.id] } ``` Outputs are recorded into the state file on `apply`, which is what makes them readable later. Treat them as an API rather than as debug printing: once another configuration reads `vpc_id`, renaming it is a breaking change for someone else's plan. A common surprise: `terraform_remote_state` exposes only **root** outputs. If the value is produced inside a child module, the root has to re-export it: ```hcl output "vpc_id" { value = module.vpc.vpc_id } ``` ## The consuming side: the terraform_remote_state data source The consumer declares a data source that names the producer's backend type and the same configuration that backend was initialised with: ```hcl data "terraform_remote_state" "network" { backend = "s3" config = { bucket = "acme-tfstate" key = "network/prod/terraform.tfstate" region = "eu-west-1" } } resource "aws_instance" "app" { subnet_id = data.terraform_remote_state.network.outputs.private_subnet_ids[0] } ``` The address is always `data.terraform_remote_state.<NAME>.outputs.<OUTPUT_NAME>`. There is no way to reach anything else — not resource attributes, not child-module state, not variables. That restriction is deliberate: it keeps the seam to whatever the producer chose to publish. ## What it actually does at plan time The data source reads the remote state **object** directly using the consumer's own credentials. It does not call the producing team's pipeline, and it does not take the producer's state lock — it is a plain read. Two consequences follow. First, permissions. For an S3 backend the consumer's role needs `s3:GetObject` on that state key, plus `kms:Decrypt` if the bucket is encrypted with a customer-managed key. State files contain far more than the outputs, so granting this grants read of everything recorded in the producer's state, including attributes that are secret in practice. Sensitivity marks on outputs propagate into the consumer's plan output, but they are not an access control on the file itself. In HCP Terraform the equivalent is a workspace setting: the producing workspace must explicitly share its state with the consumer. Second, freshness. What you read is what the producer's **last apply** recorded, not what exists in the cloud right now. If someone changed the VPC out of band, or the producer has an unapplied change sitting in a pull request, the consumer happily plans against the recorded value. ## Ordering: the producer applies first Because the read has no way to say "not known yet", ordering across the seam is manual. On a green-field environment the producer must be applied before the consumer is even planned, otherwise the consumer fails with an error about a missing output. Inside a single configuration Terraform derives that ordering from the graph; across configurations, you own it — in the runbook, in the pipeline, or by bootstrapping environments in a fixed order. ## When to reach for something else The data source is the fastest thing to write, and for two configurations owned by the same team it is usually right. It becomes a poor fit when the two sides sit on opposite sides of a trust boundary, because the read is all-or-nothing on the whole state file, or when the consumer is not Terraform at all — an application or a script cannot sensibly parse someone's state. In those cases the value is normally looked up live through a provider data source by tag or name, or published deliberately to a store such as SSM Parameter Store, where the permission can be scoped to that one key.

  • The value you need is produced inside a child module of the other configuration. Can you read it?
    Not directly. `terraform_remote_state` exposes only the root module's outputs, so the producing configuration has to re-export it at the root — `output "vpc_id" { value = module.vpc.vpc_id }` — and apply again. Until that apply lands, the output is not in the state file and the consumer's plan errors on the missing key.
  • What happens if you plan the consumer before the producer has ever been applied?
    It fails rather than deferring. The state object either does not exist or has no such output, and Terraform reports an unsupported attribute on the data source. There is no cross-configuration graph edge to make it wait, so the ordering — producer applied first — is yours to enforce in the runbook or pipeline.
  • Does reading remote state take the other configuration's lock?
    No. It is a plain read of the state object, so it neither acquires nor waits for the producer's lock, and it cannot block the producing team's apply. That also means it can read a state file mid-apply and see a partially updated view, which is one more reason to treat cross-state reads as eventually consistent.

saying these in an interview costs you the question

  • Thinks you can reference any resource attribute from another state
  • Believes remote state reads the live cloud, not the recorded file
  • Forgets the producer must apply before the consumer can plan
  • Assumes a child module's output is visible without re-exporting
  • Thinks a sensitive output hides the value from state readers

context