← Back to Blog RSS

Terraform Workspaces: A Complete Guide to Setup and Scaling

Terraform State Management Infrastructure Best Practices

A Terraform workspace is a lightweight, built-in way to run one configuration against multiple separate states. It solves the switching problem, but doesn't change how the underlying state is stored or locked, which can turn into a bottleneck as workspace count and resource count grow.

TL;DR
$ cat terraform-workspace.tldr
• A Terraform workspace lets you run the same configuration against separate state files, without duplicating code for each environment.
• Workspace commands such as new, list, select, and delete manage switching, but they don't change how the underlying state is stored or locked.
• Terraform Cloud and Enterprise turn workspaces into a managed concept with extra controls, which works differently from the CLI-only version.
• Once a team runs dozens of workspaces against one flat-file backend, locking and refresh times become the real bottleneck, rather than the workspace command itself.

Every Terraform configuration eventually needs to run against more than one environment. Dev needs its own resources, staging needs its own, production definitely needs its own, and copying the whole codebase into three folders just to keep them apart feels like the wrong decision.

A Terraform workspace is the built-in mechanism for solving this problem: one configuration, multiple separate states, switched with a single command.

At first glance, it looks almost too simple. You create a workspace, you select it, you apply, and Terraform quietly starts reading and writing a different state file behind the scenes. However, it has limitations.

This guide defines what a Terraform workspace is and covers the full command set you'll use day to day, how teams structure workspaces across dev, staging and production, how the concept changes once you're inside Terraform Cloud or Terraform Enterprise, and where the model starts to strain as a team scales up.

What is a Terraform workspace?

A Terraform workspace is a named instance of state tied to a single Terraform configuration.

The configuration stays exactly the same. What changes is which state file Terraform reads from and writes to when you plan or apply. Switch workspaces and your .tf files don't move, your variables don't change unless you tell them to, and your provider blocks stay put. Only the state changes underneath everything else.

Every Terraform configuration already has a workspace. The moment you run terraform init, Terraform creates a workspace named default and starts tracking state under it. You may have been using Terraform CLI workspaces the entire time without noticing.

A Terraform workspace is essentially a way to maintain separate state files for the same Terraform configuration, so the same set of resources can exist multiple times, once per environment, team, or deployment, without duplicating a single line of HCL.

Terraform workspace commands

The command set is small, and most of it does exactly what it says. Creating a new workspace is the natural starting point.

Running terraform workspace new dev creates a workspace called dev and switches into it in the same step, so the very next plan or apply reads and writes state under that name instead of the default.

$ terraform workspace new dev

Once more than one workspace exists, terraform workspace list becomes the command you use to see what's there. It prints every workspace tied to the current configuration and marks the currently selected workspace with an asterisk, so you can see at a glance which one you're about to run against.

$ terraform workspace list

It's worth checking the active workspace before running anything. terraform workspace show prints just the current workspace name, with nothing else to scan through.

$ terraform workspace show

Switching between existing workspaces uses terraform workspace select, which moves you into a workspace that already exists without creating a new one. Running it against a named workspace, such as terraform workspace select staging, moves you there in one step.

$ terraform workspace select staging

When a workspace has served its purpose, terraform workspace delete removes it, though Terraform refuses to delete a workspace that still has resources tracked in its state. You'll need to destroy or migrate that state first.

$ terraform workspace delete dev

The active workspace name is also available inside your own configuration, through terraform.workspace.

A common pattern uses it inside a variable or a conditional to change an instance size, a resource name, or a tag depending on which workspace is currently selected, so the same module can size things differently for dev and production without a single if statement outside of Terraform itself.

This workspace interpolation sequence is one of the more useful parts of Terraform CLI workspaces, as the environment name itself becomes something your code can react to.

Structuring Terraform workspaces for multiple environments

One workspace per environment is the most common way to manage multiple environments with a single Terraform configuration.

A team running dev, staging, and production typically keeps one root module and one backend configuration, switching between three workspaces as work moves from a dev workspace through staging and into production.

Inside the configuration, terraform.workspace decides what varies. A variable might set a smaller instance type when the workspace is dev, a conditional might skip provisioning a read replica outside of production, and naming conventions built from the workspace name keep resources from colliding when they're all created from the same code.

This variation is what lets a single configuration create multiple deployments, and multiple instances of the same infrastructure, without maintaining several near-identical copies of the same files.

Not every team structures things this way. A good number choose separate directories, or entirely separate root modules, one per environment, each with its own state file and often its own backend configuration rather than sharing one.

Terraform never mixes state between them, because there's no shared workspace mechanism connecting them in the first place. Teams tend to opt for this solution instead of workspaces when they want stronger isolation between environments, deployments requiring separate credentials and access controls, or when the blast radius of a mistaken command needs to be obvious from the directory you're standing in, not from checking which workspace happens to be selected.

Neither approach is necessarily better. It depends on how much you and your team trust yourselves to always check the currently selected workspace before running terraform plan, against how much duplication it's willing to tolerate in exchange for that isolation being structural rather than procedural.

HCP Terraform and Enterprise workspaces

HCP Terraform and Terraform Enterprise use the term workspace for something more specific than the CLI concept covered above.

An HCP Terraform (previously known as Terraform Cloud) workspace and a Terraform Enterprise workspace behave the same way: as a managed unit inside the platform's UI, usually tied to a VCS repository, with its own variables, its own run history, and its own access controls attached directly to it.

Where a CLI workspace is just a name that changes which state file gets read, an HCP Terraform or Terraform Enterprise workspace is closer to a project record.

Credentials and access controls for different internal teams live on the workspace itself, rather than being something set through environment variables or a tfvars file on somebody's own machine.

That structure unlocks the features people usually rely on HCP Terraform or Terraform Enterprise for in the first place.

Runs happen remotely instead of on a developer's laptop, policy can be enforced before an apply is allowed to proceed, and access controls decide which team members can plan, which can approve, and which can only view.

For an organisation trying to manage multiple deployments across different internal teams, that centralised model solves a coordination problem that CLI workspaces alone were never built to solve.

Of course, this managed model is one option, not the only one. Plenty of teams pair a self-hosted or remote backend with ordinary CLI workspaces instead because they want more control over where state actually lives and how it's secured, rather than handing that over to a managed platform.

Switching between Terraform workspaces safely

The real risk with Terraform CLI workspaces is that you run terraform apply without checking which workspace is actually active. Workspaces don't warn you.

If production and staging are both reachable from the same terminal session, and someone selects the wrong one out of habit, Terraform will happily apply changes meant for staging against production state, because as far as Terraform is concerned that is the currently selected workspace, and it's just doing what it's told.

Here are a few habits that help you prevent that from happening.

None of this makes it impossible to select the wrong workspace, but it does make it harder to select it without noticing.

Where workspace-based isolation breaks down at scale

Everything covered so far describes what a workspace is good at. It gives a name to a state, and it makes it easy to switch between them.

What a workspace does not do is change how that state is stored, or how it's locked once more than one person needs to touch it. Each workspace still produces its own separate state file, sitting behind whatever backend the team chose when they set up the configuration, most commonly a remote backend such as an object store.

Once workspace count and resource count both grow, managing multiple workspaces can become a challenge.

Observation

A backend built around one global lock per state means every apply against a workspace holds that whole state file's lock for the duration of the run, whether the change touches three resources or three hundred.

Run a handful of workspaces against genuinely shared infrastructure, the kind where multiple team members and multiple pipelines are all trying to plan or apply around the same time, and applies start queuing behind each other.

Refresh times climb as resource count grows inside each state, because checking a handful of resources still means reading the entire file. A one-line change to a single resource still triggers a full state read before Terraform can tell you what actually changed.

This kind of scaling pain shows up in industry-wide research too, not just in individual complaints. HashiCorp's 2025 Cloud Complexity Report found that 52% of organisations rank the complexity of managing multi-cloud and hybrid environments among their top infrastructure challenges, and that the average organisation already runs five separate tools just to manage its cloud environment in the first place.

And that's before backend behaviour and locking are even part of the picture.

Of course, none of these present a flaw in workspaces. Workspaces do exactly what they were designed to do: They give a configuration a suitable isolation mechanism between environments, and let a team switch between them with one command.

The bottleneck that shows up at scale is a storage and locking problem sitting underneath the workspace, not a mistake in how the workspace command works.

Fixing the state bottleneck with Stategraph

Stategraph exists to fix that bottleneck.

It's a replacement for how Terraform state is stored and locked, not a replacement for Terraform itself, and not a replacement for workspaces. Terraform workspace commands, terraform.workspace references, and existing Terraform code all keep working exactly as they do today.

Instead of writing state out as one flat JSON file per workspace, Stategraph stores it as a graph in PostgreSQL, with resources as rows and the dependencies between them as queryable relationships rather than buried attribute strings.

Because the graph already knows which resources are actually connected to a given change, a plan or apply only has to touch the affected part of the graph, not the whole state.

Stategraph has demonstrated importing 20,000 resources in three seconds, with queries against that same state completing in under five seconds. That kind of speed holds up whether a workspace has a hundred resources or several thousand.

A traditional backend applies eight resources serially in 25 seconds while Stategraph completes all of them in 5 seconds by running independent resources concurrently. A traditional backend applies eight resources serially in 25 seconds while Stategraph completes all of them in 5 seconds by running independent resources concurrently.

Instead of one global lock covering an entire state file, Stategraph locks at the level of the resource.

Design Principle

A team running many workspaces against shared infrastructure stops watching applies queue behind each other, as two workspaces touching genuinely different resources are no longer forced to wait on the same lock just because they happen to share a backend.

Refreshes stop scaling with total resource count, since a query only has to read the rows it actually needs.

None of this requires a team to restructure environments. Whether a team keeps its dev workspace, staging workspace, and production workspace exactly as they are, or runs separate root modules per environment instead, adopting Stategraph doesn't require changing a single line of Terraform configuration. It's a backend swap, not a rewrite.

Conclusion

The Terraform workspace command is not a bottleneck.

Creating one, listing them, switching between them, none of that gets slower as a team grows. What sits behind the command is what determines whether workspaces stay pleasant to use at ten resources and painful at ten thousand.

Which environment structure fits, whether that's a workspace per environment or separate directories with their own state files, is a decision that depends on how a specific team works, not on a universal best practice.

Whether or not the backend underneath can actually handle the result once workspace count and resource count both climb is what's important. A tool for managing multiple environments shouldn't force a choice between simplicity and speed.

To see how a graph-based backend handles that in practice, read the Stategraph docs to see the model in more detail.