Terraform Workspaces
The idea in one sentence
Section titled “The idea in one sentence”A Terraform workspace lets one configuration and one backend produce several independent state files, switched by name from the CLI, with zero duplicated .tf code.
Creating and switching workspaces
Section titled “Creating and switching workspaces”Every Terraform configuration starts with a single workspace named default. You create and switch between additional named workspaces with three commands:
# Create a new workspace and switch to it immediatelyterraform workspace new dev
# List every workspace; the active one is marked with an asteriskterraform workspace list# default# * dev# staging# prod
# Switch to an existing workspaceterraform workspace select stagingEach workspace gets its own state file, but every workspace still reads the exact same .tf files and points at the exact same backend configuration. Running terraform plan in the dev workspace only ever looks at the dev state; switch to staging and Terraform behaves as if it has never seen the staging resources before, because as far as that state file is concerned, it has not.
Varying behavior with terraform.workspace
Section titled “Varying behavior with terraform.workspace”The active workspace name is available inside your configuration as the built-in value terraform.workspace. Interpolating it lets one set of .tf files behave differently depending on which workspace is currently selected:
locals { instance_size = { dev = "t3.micro" staging = "t3.small" prod = "m5.large" }}
resource "aws_instance" "app" { ami = data.aws_ami.app.id instance_type = local.instance_size[terraform.workspace]
tags = { Name = "app-${terraform.workspace}" }}This is genuinely DRY: there is exactly one aws_instance block, one place to fix a bug in it, and the instance size and name still come out different per workspace. No copy-pasted directories, no duplicated resource definitions.
What a workspace does not isolate
Section titled “What a workspace does not isolate”Here is the limitation to hold onto: a workspace only isolates state. It changes nothing else. Every workspace built from the same configuration shares:
- The exact same backend configuration — same S3 bucket, same region, same account.
- The exact same provider configuration — same AWS credentials, unless you write conditional logic to vary them.
- The exact same
.tfcode — there is no hard boundary preventing adevworkspace from containing a resource block that, if misconfigured, is just as capable of touching production-scale infrastructure as any other workspace.
That makes workspaces a good fit for lightweight, structurally identical variation — a short-lived feature-branch preview environment that just needs its own state and a smaller instance size is a great use case. It is a poor fit the moment an environment needs a genuinely different AWS account, a different region, or different credentials from the others, because a workspace cannot express any of that; it is just a name.
The sharper, everyday risk is human, not architectural: the active workspace is a piece of CLI-selected state that lives outside your .tf files entirely. Nothing stops an engineer from believing they are in dev, running terraform apply, and discovering only afterward that the terminal was still sitting in prod from an hour earlier. There is no separate credential, no separate account boundary, no confirmation prompt tied to the workspace name — just a string that is easy to forget.
flowchart LR cfg["One .tf config + one backend"] --> dev["workspace: dev (own state file)"] cfg --> staging["workspace: staging (own state file)"] cfg --> prod["workspace: prod (own state file)"]