Terraform — Part 11 — Managing Environments: Workspaces vs Directories
Every example so far has run against a single, implicit environment. This part covers the two real patterns for running the same configuration against dev, staging, and production without them colliding — and which one to actually reach for.
A named state file within one working directory and one backend. Lightweight, same credentials for every workspace.
A completely separate copy of the configuration, with its own state, backend, and credentials.
The workspace every configuration starts in, and the one already used throughout this series.
Introduction
Every example in this series has run in a single, unnamed environment — one state file, one set of credentials, one backend. Real infrastructure work almost never stays that simple: the same configuration usually needs to exist in dev, staging, and production, without a change tested in dev being able to touch production by accident.
CLI Workspaces
Every configuration already has at least one workspace, named default — every apply in this series so far has run inside it without that ever being visible. A workspace is a named state file within the same working directory and the same backend:
# List existing workspaces (the current one is marked with *)
terraform workspace list
# Create and switch to a new workspace
terraform workspace new staging
# Switch to an existing workspace
terraform workspace select default
# Show which workspace is currently active
terraform workspace show
Inside a configuration, terraform.workspace resolves to the name of the currently active workspace, commonly used for naming or tagging so resources in different workspaces don’t collide:
resource "random_pet" "server" {
prefix = "${terraform.workspace}-server"
length = 2
}
Where Workspace State Lives
With local state, each non-default workspace’s state file lives inside a terraform.tfstate.d directory, one subfolder per workspace — treat that directory with the same care as a regular local state file. With a remote backend, the backend itself is responsible for keeping each workspace’s state separate, typically by appending the workspace name to the configured state path.
What Workspaces Are Good For
HashiCorp’s own guidance is that workspaces suit creating a parallel, disposable copy of infrastructure — testing a change in a workspace tied to a feature branch, for instance, without touching the workspace anything else is running in. Because a new workspace is nearly free to create and destroy, this fits short-lived, throwaway copies well.
What Workspaces Are Not Good For
The same guidance is direct about the limit: workspaces are not appropriate for system decomposition, and specifically not for the case where separate environments need separate credentials and access controls. Every workspace in a working directory shares the same backend configuration — the same credentials that can reach the staging workspace’s state can reach default‘s too.
apply, workspaces alone don’t provide that boundary.
The Alternative: Separate Directories
The other common pattern is simply running the same configuration from multiple working directories — environments/dev/, environments/staging/, environments/prod/ — each with its own backend configuration, and therefore its own credentials and its own genuinely separate state.
environments/
dev/
main.tf
backend.tf
staging/
main.tf
backend.tf
prod/
main.tf
backend.tf
Each directory gets its own terraform init, its own provider plugin cache, and its own independent backend block — which is exactly what makes real credential separation possible: production’s backend can point at a bucket only a restricted set of people or pipelines can reach, while dev’s is open to the whole team.
Workspaces vs Directories
| Aspect | CLI Workspaces | Separate Directories |
|---|---|---|
| Backend / credentials | Shared across all workspaces. | Independent per directory. |
| Setup cost | Nearly free — one command to create. | Higher — separate init, separate plugin cache per directory. |
| Best fit | Short-lived, disposable copies (feature branches, PR previews). | Genuinely distinct environments needing real access separation (dev vs prod). |
| Access control granularity | None — same credentials reach every workspace. | Full — each directory’s backend can require different credentials entirely. |
Quick Reference Summary
| Command | Purpose |
|---|---|
terraform workspace list |
Lists all workspaces in the current backend. |
terraform workspace new <name> |
Creates and switches to a new workspace. |
terraform workspace select <name> |
Switches to an existing workspace. |
terraform.workspace |
Expression referencing the currently active workspace’s name. |
terraform.tfstate.d |
Local directory holding non-default workspace state files. |
Final Thoughts
Neither pattern is universally correct — the deciding factor is whether the environments genuinely need different access boundaries. A quick parallel copy for testing a branch is what workspaces were built for. Production needing its own credentials, separate from everything else, is what directories are built for.
Terraform — Part 12 — Terraform Best Practices and Common Pitfalls. The capstone post, pulling together the practices from every earlier part into one working reference.