Choosing How Terraform Configurations Share Values: Remote State, Data Sources, or Variables
Three supported ways to share values between Terraform configurations — remote state, provider data sources, and variables — compared on coupling, access, and failure modes, with a working example.
15 Aug 2025, 05:05 UTC

Splitting infrastructure into multiple Terraform configurations is easy. The hard part is getting values out of one and into another: the networking stack produces a VPC ID, and the application stack needs it. Terraform supports three well-established ways to do this, and the right choice depends on coupling, trust boundaries, and who owns each side — not on correctness, since all three work.
This guide compares the options, then walks through a concrete setup you can validate yourself. Examples assume Terraform 1.x with an S3 backend, but the decision logic applies to any backend.
The three supported options
- terraform_remote_state data source — the consumer reads the producer's outputs directly from its state file.
- Provider data sources — the consumer looks the resource up through the cloud API (for example, finding a VPC by tag) and never touches the producer's state.
- Explicit input variables — a pipeline, tfvars file, or parameter store passes the value in; Terraform configurations never reference each other at all.
Comparison
| Criterion | terraform_remote_state | Provider data source | Input variables |
|---|---|---|---|
| Coupling to producer | High — depends on output names and state location | Low — depends only on a tag or name | Lowest — plain values, no Terraform linkage |
| Freshness | Always reflects last applied state | Live API lookup at plan time | Only as fresh as whatever wrote the value |
| Access required | Read access to the producer's entire state file | Cloud API read permissions | None beyond the pipeline |
| Works for non-Terraform resources | No | Yes | Yes |
| Failure mode | Plan error if output renamed or state unreadable | Plan error if lookup matches zero or many resources | Silent staleness if the producer changes |
| Best fit | Tightly related stacks, same team, same backend | A stable lookup key exists (tags, names) | Cross-team or security-boundary sharing |
Trade-offs that actually bite
Remote state is a trust decision, not just a convenience. Anyone who can run terraform plan in the consumer needs read access to the producer's state file, and state files often contain sensitive values beyond the outputs you intend to share. Avoid terraform_remote_state across team or security boundaries.
Output names are not versioned contracts. If the producer renames vpc_id to main_vpc_id, every consumer fails at plan time with no warning to the producer. Treat outputs as a published API and review renames accordingly.
Data sources shift the failure earlier and differently. A lookup by tag fails at plan time if the tag is not unique or the resource does not exist yet — which also means you must order applies explicitly (networking first, then consumers), because Terraform cannot infer a dependency across configurations.
Variables push orchestration into your pipeline. Loosest coupling, but something has to fetch the value, keep it current, and detect drift. A parameter store (such as AWS SSM Parameter Store) written by the producer and read by the consumer's pipeline is the common pattern.
Concrete example: remote state between two configurations
The producer (in its own directory, with its own state) exposes one output:
# producer/outputs.tf
output "vpc_id" {
description = "ID of the shared VPC. Treat this name as a stable contract."
value = aws_vpc.shared.id
}Run terraform apply in the producer directory, then confirm the contract with terraform output vpc_id. Note the value.
The consumer reads it via the same backend. Replace the bucket, key, and region placeholders with your backend details:
# consumer/data.tf
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "<your-state-bucket>"
key = "network/terraform.tfstate"
region = "<your-region>"
}
}
resource "aws_subnet" "app" {
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
cidr_block = "10.0.50.0/24"
}Run terraform init (required so the backend configuration is installed), then terraform plan. The plan should show the subnet being created in the VPC ID you noted from the producer. The identity running plan needs read permission on the state object — that is the access-control cost described above.
Validating the decision
- Confirm the remote-state path resolves: the consumer's plan shows the exact ID from
terraform outputin the producer. - Cross-check with a provider data source: add
data "aws_vpc" "shared"filtered by the VPC's tag and confirm it returns the same ID. If both agree, either mechanism is viable and you can choose on coupling grounds. - Test the failure mode deliberately: rename the producer output, apply, and re-plan the consumer. You should see an error about a missing output attribute — this is the coupling risk made visible, and it is why output renames need the same care as API breaking changes.
Limitations
Backend configuration syntax and state-locking behavior differ across S3, AzureRM, GCS, and Terraform Cloud; check your backend's current documentation before copying the config block. Data source lookups depend on your tagging discipline being enforced. And none of these mechanisms version the shared contract for you — that remains a process decision, whichever option you pick.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.