Back to Blog

Structuring Terraform Across Multiple States for the Terraform Associate Exam: Root Modules, terraform_remote_state & Sharing Data Between Configurations

One giant state file is slow, risky, and a collaboration bottleneck. Learn how to split Terraform into multiple states, why a root module maps to exactly one state, the three ways to share data between configurations (terraform_remote_state, data sources, and SSM), and the apply-ordering and blast-radius trade-offs — for the HashiCorp Terraform Associate (003/004) exam.

By Sailor Team , September 13, 2026

Every Terraform project starts as a single directory with a single state file, and for a weekend project that is exactly right. But as the configuration grows — networking, databases, clusters, applications, all in one terraform apply — that single state turns into three problems at once: a plan that takes minutes because it refreshes everything, a blast radius where one careless change can ripple through unrelated infrastructure, and a collaboration bottleneck where every engineer waits on a single state lock. The professional answer is to split one state into many. Doing that well — and knowing how the pieces then talk to each other — is a core state-management skill, and the HashiCorp Terraform Associate exam tests the mechanics directly.

This guide is the architectural companion to two posts that own the fundamentals: the Terraform state management guide covers backends, locking, and what state actually stores, and the Terraform data sources guide covers the terraform_remote_state data source in isolation. Here we zoom out to the decision that surrounds both: when should you split state, and how should the resulting configurations share information?

One State Is Fine — Until It Isn’t

A single state is the simplest thing that works, and premature splitting is its own anti-pattern. Split only when one of these pressures shows up:

PressureWhat it looks likeWhy splitting helps
Slow plansplan refreshes hundreds of resources you didn’t touchEach config only refreshes its own resources
Blast radiusA typo in an app change could affect the shared VPCA mistake is contained to one state
Team bottleneckEveryone waits on one state lockTeams apply their own states in parallel
Mixed rates of changeNetworking changes yearly; apps change dailySlow-moving infra isn’t re-planned on every app deploy
Different ownershipPlatform team owns the cluster; app teams own workloadsState boundaries follow team boundaries

The guiding principle is to draw state boundaries along lifecycle and ownership lines: things that change together, and are owned by the same people, belong in the same state. A common layering is networking → platform (clusters, databases) → applications, each in its own state, each applied by the team that owns it.

Exam signal: phrases like “reduce blast radius”, “allow teams to work independently”, or “networking rarely changes but apps deploy daily” are pointing at splitting state, not workspaces and not one big configuration.

The Concept That Trips Everyone Up: Root Module = One State

Before splitting anything, nail this distinction, because the exam leans on it:

  • A root module is the directory you actually run terraform in. It has exactly one state file in exactly one backend.
  • A child module is a reusable package of resources that a root module calls. A child module has no state of its own — its resources are stored in the calling root module’s state.

So “multiple states” does not mean “multiple modules.” You can have a dozen child modules and still one state. Multiple states means multiple root modules, each with its own backend configuration and its own terraform apply. Calling a module never creates a new state; standing up a new root directory with its own backend block does.

# Each of these directories is a ROOT module = one state each
# infra/
#   networking/   -> backend "s3" { key = "networking/terraform.tfstate" }
#   platform/     -> backend "s3" { key = "platform/terraform.tfstate" }
#   apps/         -> backend "s3" { key = "apps/terraform.tfstate" }
# modules/
#   vpc/          -> a CHILD module, no state, called by networking/

For how child modules are authored, versioned, and consumed, see the Terraform modules complete guide. For how the backend and state file behave, the state management guide is the reference.

Sharing Data Between Configurations

The moment you split, a new question appears: the apps config needs the VPC and subnet IDs that the networking config created. There are three ways to bridge that gap, and knowing the trade-offs is the heart of this topic.

Option 1: The terraform_remote_state Data Source

The most direct approach. The consumer reads the outputs of the producer’s state file:

# In the networking root module — expose what others need
output "private_subnet_ids" {
  value = aws_subnet.private[*].id
}

# In the apps root module — read the networking state's outputs
data "terraform_remote_state" "networking" {
  backend = "s3"
  config = {
    bucket = "mycompany-tfstate"
    key    = "networking/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_ids[0]
}

Three facts the exam loves about terraform_remote_state:

  1. It reads outputs only. You cannot reach into another state and read an arbitrary resource attribute — if a value isn’t declared as an output, it isn’t accessible.
  2. The consumer needs read access to the producer’s backend. That state file may contain sensitive values in plain text, so granting read access is a real security decision, not a formality.
  3. It tightly couples the two configurations to each other’s backend location and output contract. Rename an output and every consumer breaks.

Option 2: Provider Data Sources on the Real Resources

Instead of reading Terraform’s state, read the live infrastructure through a normal provider data source:

data "aws_vpc" "main" {
  tags = { Name = "production" }
}

data "aws_subnets" "private" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.main.id]
  }
}

This decouples the two configurations entirely — the apps config doesn’t know or care that Terraform built the VPC. It only needs a stable identifier (a tag, a name) to find it. The cost is that you depend on those tags staying consistent, and the lookup can be more verbose.

Option 3: A Published Value Store (e.g., SSM Parameter Store)

The producer writes its outputs to a neutral store; consumers read from it:

# Producer (networking) publishes an ID
resource "aws_ssm_parameter" "vpc_id" {
  name  = "/platform/vpc_id"
  type  = "String"
  value = aws_vpc.main.id
}

# Consumer (apps) reads it — no access to the other state required
data "aws_ssm_parameter" "vpc_id" {
  name = "/platform/vpc_id"
}

This is the loosest coupling of the three: consumers never touch the producer’s state, non-Terraform tools can read the same values, and the contract is an explicit, documented parameter path. The cost is extra plumbing and a store to manage.

Here is the comparison to keep in your head:

MethodCouplingNeeds state access?ReadsBest when
terraform_remote_stateTightYes (producer’s backend)Outputs onlySame team owns both configs
Provider data sourceLooseNoLive resource attributesStable tags/names exist
SSM / value storeLoosestNoPublished parametersCross-team or cross-tool sharing

Who Runs What, and In What Order?

The biggest behavioral change after splitting: Terraform no longer orders operations across states for you. Within one state, the dependency graph guarantees the VPC is created before the subnet. Across two states, there is no shared graph — you own the ordering. networking must be applied before apps, because apps reads networking’s outputs. If you apply apps first, the remote-state read returns nothing (or stale data) and the apply fails or produces wrong results.

In practice this ordering lives in your pipeline: a CI/CD workflow applies the layers bottom-up (networking, then platform, then apps) and top-down in reverse for destroys. The dependency is implicit — expressed by the direction of the terraform_remote_state reads — but the execution is yours to sequence. This is exactly where multiple states meet the core Terraform workflow: each state is its own init → plan → apply cycle.

Multiple States vs. Workspaces — Don’t Confuse Them

A frequent exam trap pits splitting state by layer against workspaces, and they solve different problems:

  • Multiple root modules split one environment into independent pieces by lifecycle and ownership (networking vs apps).
  • Workspaces create multiple state files from one configuration — typically to stamp out the same infrastructure for dev, staging, and prod from a single directory.

They compose: you might have a networking root module that itself uses a workspace per environment. But for strong production isolation, many teams prefer a directory (and backend) per environment over workspaces, because a separate backend can’t be switched into by accident and can have its own access controls. The Terraform workspaces guide covers that decision in depth; the short version for the exam is that workspaces share one backend and configuration, while separate root modules do not.

Anti-Patterns to Avoid

  • The monolith. One state for everything: slow plans, huge blast radius, constant lock contention. Split along lifecycle lines.
  • The confetti. A separate state for every tiny resource: now you drown in terraform_remote_state plumbing and cross-state ordering. Split by ownership and rate of change, not by resource count.
  • Reading non-outputs. Expecting to pull an arbitrary attribute through terraform_remote_state — only declared outputs are visible.
  • Circular dependencies. State A reads B while B reads A. There is no graph to resolve it across states; redesign the boundary.
  • Leaking secrets through remote state. Because terraform_remote_state grants read access to a state file that may hold sensitive values in plain text, prefer a published value store (or provider data sources) when the consumer shouldn’t see everything the producer knows. See how state treats sensitive data in the state management guide.

Build the Instinct with Practice

Structuring state is one of those topics where the concepts are quick to read but the exam questions are subtle — a scenario about reducing blast radius, a question on what terraform_remote_state can and can’t read, a workspaces-vs-directories trade-off. The reliable way to make those reflexive is scenario-style practice with explanations. The Sailor.sh Terraform Associate mock exams include state, remote-state, and project-structure items, each with a walk-through of why the right answer wins. Warm up first with the free Terraform Associate practice questions, then round out the objective with the state management, data sources, and modules guides.

Frequently Asked Questions

What does terraform_remote_state actually read?

It reads the output values of another Terraform configuration’s state file — nothing else. You declare output blocks in the producer configuration, and the consumer accesses them as data.terraform_remote_state.<name>.outputs.<output_name>. Any resource attribute that isn’t exposed as an output is invisible to the consumer, which is why designing a clean output contract matters when you split state.

Does calling a module create a separate state file?

No. A child module’s resources are stored in the calling root module’s state file — modules do not have their own state. You get a separate state only by creating a separate root module (a directory with its own backend configuration) and running terraform in it. This is the single most important distinction when reasoning about “multiple states.”

How do I share data between two Terraform configurations?

Three common ways: the terraform_remote_state data source (reads the other config’s outputs; tight coupling and needs state access), a provider data source that looks up the live resource by a stable tag or name (loose coupling), or a value store like AWS SSM Parameter Store where the producer publishes values and the consumer reads them (loosest coupling, no state access needed). Choose based on how tightly the two configurations should be coupled and whether the consumer should see the producer’s state.

Should I use workspaces or separate directories for environments?

Workspaces give you multiple state files from one configuration and backend — convenient for identical, short-lived environments. Separate directories (each with its own backend) give stronger isolation and independent access control, which is why many teams prefer them for production. Both are valid; the exam expects you to know that workspaces share a single backend and configuration while separate root modules do not.

Why did my apply fail after splitting state into layers?

Almost always an ordering problem. Terraform only builds a dependency graph within a single state, so it won’t automatically apply your networking layer before your app layer. You must sequence the applies yourself — bottom-up for creates (networking, then platform, then apps) and top-down for destroys — usually in a CI/CD pipeline. If a consumer applies before its producer, the terraform_remote_state read returns stale or missing outputs.

Is splitting state always a good idea?

No. A single state is simpler and perfectly fine until you feel real pressure — slow plans, an uncomfortable blast radius, lock contention, or diverging ownership. Splitting too finely creates its own overhead: cross-state plumbing, ordering complexity, and more places for drift to hide. Split along lifecycle and ownership boundaries, and only when the pain is real.

Conclusion

Splitting Terraform into multiple states is how you keep a growing platform fast, safe, and workable across teams — but it introduces two responsibilities Terraform used to handle for you. First, remember that a root module maps to exactly one state, so “multiple states” means multiple root directories, not multiple child modules. Second, once state is split, you own the wiring and the ordering: choose how configurations share data (terraform_remote_state for tight coupling, provider data sources or a value store for looser coupling), and sequence applies bottom-up because Terraform no longer orders across states. Draw your boundaries along lifecycle and ownership, avoid both the monolith and the confetti, and the “how should we structure this?” scenarios on the Terraform Associate exam — and in your real projects — resolve into a clear, defensible answer.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now