A workspace is a named, isolated state instance for a single configuration directory. Same code, separate state, separate infrastructure — which sounds like the complete answer to multi-environment management, and is not.
Topic 1: The Commands
terraform workspace list # * marks the active one
terraform workspace new staging # creates AND switches
terraform workspace select prod
terraform workspace show
terraform workspace delete dev
Every project starts in a workspace called default, which cannot be deleted.
$ terraform workspace list
* default
dev
prod
Topic 2: Using the Workspace in Configuration
The active workspace is available as terraform.workspace:
resource "aws_sqs_queue" "main" {
name = "${terraform.workspace}-queue"
}
locals {
environment = terraform.workspace
instance_type = {
default = "t3.micro"
dev = "t3.micro"
prod = "t3.large"
}[terraform.workspace]
}
That map-lookup pattern is how workspaces drive more than naming. It is also where the approach starts showing strain — every difference between environments becomes another lookup keyed by workspace name, scattered through the configuration.
Topic 3: Where State Lives
With local state, the default workspace uses terraform.tfstate and others use terraform.tfstate.d/<name>/terraform.tfstate. The currently selected workspace is tracked in .terraform/environment.
Both are internal implementation details subject to change. Never build tooling that reads them; use terraform workspace show instead.
Workspaces require a backend that supports them. Object-storage backends do, storing each workspace under a separate key prefix.
Topic 4: The Delete Command Is a Trap
terraform workspace delete removes the state. It does not destroy the infrastructure.
Delete a workspace with live resources and those resources keep running, keep billing, and are now managed by nothing. There is no record they exist. Recovering means finding them by hand and importing them into a recreated workspace.
The correct order is always:
terraform workspace select dev
terraform destroy # destroy FIRST
terraform workspace select default
terraform workspace delete dev # then delete
Terraform will refuse to delete a workspace whose state is non-empty unless you force it — but the refusal is easy to override in a hurry, and the failure is silent afterwards.
Topic 5: When Workspaces Fit, and When They Do Not
Good fit:
- Same resource topology, different parameters.
- Short-lived, isolated copies for testing or QA.
- Small teams and prototypes where the overhead of separate directories is not worth it.
- Per-developer environments from one configuration.
Poor fit:
- Environments with genuinely different architectures. Production has a load balancer and a read replica; dev does not. Expressing that with
count = terraform.workspace == "prod" ? 1 : 0across a dozen resources produces configuration nobody can read. - Anything requiring different provider configurations, different accounts, or different backends per environment — the backend is fixed for the whole directory.
- Estates where you need strong blast-radius separation between environments. One directory, one set of credentials, one careless
workspace selectaway from applying dev changes to prod.
The scaling limit, stated plainly:
Driving everything from the workspace name alone does not scale. You need real per-environment input variables, and at that point you are maintaining a variables file per workspace — which is the directory-per-environment pattern with extra steps and weaker isolation.
Managed Terraform offerings address this by attaching variables to a workspace as first-class objects, including ones marked sensitive so they cannot be read back. That is the version of workspaces that scales; the CLI-only version has a lower ceiling.
Topic 6: Combining Workspaces and tfvars
They compose, and this is the practical middle ground:
terraform workspace select prod
terraform apply -var-file="prod.tfvars"
Workspaces isolate state; tfvars customise inputs. The risk is that the two can disagree — selecting the dev workspace while passing prod.tfvars produces prod-shaped resources in dev state, and nothing stops you.
If you use this combination, derive the var-file from the workspace in your pipeline rather than passing it by hand:
terraform apply -var-file="$(terraform workspace show).tfvars"
Try it yourself: Create a workspace, apply a resource, then run terraform workspace delete on it without destroying. Read the refusal message, then check what would have happened had you forced it. Understanding that failure once is worth more than remembering the rule.
Common mistake: Reaching for workspaces because they are the feature named after environments. For a production estate with separate accounts and diverging architectures, directory-per-environment with separate backends and separate credentials is almost always the better answer — workspaces isolate state, but they do not isolate blame, credentials, or blast radius.