NovuSpark
All articles

July 24, 2026 · NovuSpark Team

Terraform Workspaces and Multi-Environment Strategies

This is the fifth and final post in our Terraform fundamentals series. It assumes the modules and remote state posts as background.

Every team running the same Terraform configuration across dev, staging, and production eventually asks the same question: what's the right way to structure that, so it's not three copy-pasted folders drifting apart over time? Terraform's built-in answer is workspaces — and it's worth understanding both what they solve and where they quietly stop being the right tool, before committing a whole team's environment strategy to them.

What a workspace actually is

A workspace is an isolated instance of state, layered on top of a single configuration.

terraform workspace new staging
terraform workspace new production
terraform workspace list
  default
  staging
* production

The asterisk marks the currently selected workspace. Every plan and apply operates against whichever workspace is currently selected, each with its own independent state file, even though every workspace shares the exact same .tf files.

resource "aws_instance" "web" {
  ami           = "ami-0c1a7f89451184c8b"
  instance_type = terraform.workspace == "production" ? "m5.large" : "t3.micro"
 
  tags = {
    Name        = "web-${terraform.workspace}"
    Environment = terraform.workspace
  }
}

terraform.workspace is a built-in reference to the current workspace's name, usable anywhere in your configuration — most commonly to vary sizing, naming, or which conditional branch of a resource applies.

What workspaces genuinely solve well

For infrastructure that's structurally identical across environments — same resources, same relationships, differing only in size and a handful of values — workspaces are a clean fit. A team standing up several short-lived, near-identical environments (feature-branch preview environments are the canonical example) benefits from exactly what workspaces provide: one configuration, cheaply instantiated multiple times, each isolated from the others.

Where workspaces stop being the right tool

The trouble starts when dev, staging, and production — which usually start structurally identical — inevitably stop being identical. Production needs a read replica that staging doesn't. Production needs a WAF in front of it that would just add latency and cost in dev. The moment that happens, the conditional logic required to keep one shared configuration correct for every environment starts to sprawl:

resource "aws_db_instance" "main" {
  # ...
  multi_az = terraform.workspace == "production" ? true : false
}
 
resource "aws_wafv2_web_acl" "main" {
  count = terraform.workspace == "production" ? 1 : 0
  # ...
}

A handful of conditionals like this are manageable. A configuration with forty of them, each independently deciding what exists in which environment, becomes something closer to an interpreter for environment-specific logic than an infrastructure definition — and it's genuinely difficult to look at the file and know what production actually contains without mentally evaluating every conditional yourself.

There's a second, sharper risk worth naming directly: workspaces share the same variable values by default. Set instance_type once, and every workspace uses that value unless you've built your own workaround (a lookup map keyed by terraform.workspace, typically) to vary it. It's easy to assume workspaces give you per-environment configuration for free — they don't; they give you per-environment state, and any actual configuration difference is something you have to build yourself.

The alternative most teams converge on

For environments that are meaningfully different — which, realistically, is most real production setups within a year or two of launch — separate root configurations per environment, sharing common logic through modules, tends to age better:

environments/
  dev/
    main.tf        # calls modules/web-app with dev-sized inputs
    backend.tf      # dev's own remote state config
  staging/
    main.tf
    backend.tf
  production/
    main.tf         # can genuinely differ from dev's structure
    backend.tf

Each environment gets its own state, its own backend configuration, and — critically — the freedom to actually diverge in structure, not just in variable values, without turning the shared configuration into a maze of conditionals. The module (from the modules post) is what keeps the genuinely shared logic in one place; each environment's root configuration decides how to call it, and is free to add things the others don't have.

A rough guide for choosing

  • Workspaces: environments that are genuinely structurally identical and short-lived — preview/PR environments, parallel test environments, sandboxes.
  • Separate root configurations + shared modules: dev/staging/production, or any set of environments you expect to meaningfully diverge in structure over time — which, in practice, is most of them eventually.

Neither is universally "correct" — they solve different shapes of the same underlying problem, and the wrong choice tends to show up as pain roughly a year in, not on day one, which is exactly why it's worth deciding deliberately up front rather than defaulting to whichever one a tutorial mentioned first.

What to actually remember from this post

  • Workspaces isolate state, not configuration — every workspace runs the same .tf files unless you build conditional logic yourself.
  • They're a strong fit for structurally identical, often short-lived environments.
  • They're a weak fit once environments genuinely diverge — which is most production setups, eventually — because the conditional logic to keep one configuration correct for every environment tends to sprawl.
  • Separate root configurations sharing common modules is the pattern most teams land on for dev/staging/production specifically, once they've outgrown workspaces for that purpose.

That closes out our Terraform fundamentals series — from your first resource through variables, modules, and remote state, to the environment-strategy decision most teams eventually have to make deliberately. If your team is navigating any of these decisions in practice, that's exactly the kind of hands-on work we build our cloud and DevOps training around.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.